Make a Simple Employee managing app in Django with Class Based Views (With Login, Logout)
Tags: python, django, tutorial, webdev
Published: 2022-04-23 15:55:09

A few months ago when I'm learning about Django class-based views, I made an app where I practice my Django skills using classes. Now, I'm here to share it in you.

## Setup
**Requirements**
django  - `pip intsall django`

1. Make a DIR with a virtual environment if you want
2. Run `django-admin startproject projectname . `
3. Run `python manage.py startapp appname`
4. Add app dependency in project

-I choose to name my `projectname` as `my_project` and `appname` as `the_app`. `my_project` and `the_app` is what we're gonna use for this one.

```python
# /my_project/settings.py

INSTALLED_APPS = [
	...,
	'the_app.apps.TheAppconfig',
]
```
5. Making app's urls.py (optional)
-You can use the project's `urls.py` but you can make another for the app to separate it from the URLs of other apps you may add in the future.
A. Create a `urls.py` inside `the_app` directory or what we have `the_app`, and then;
```python
# /the_app/urls.py

from django.urls import path

urlpatterns = [
	path('chosen url', function(), name=None),		# Function-based syntax
	path('chosen url', class.as_view(), name=None),	# Class based syntax
]
```
B. Connect `my_project/urls.py` to `the_app/urls.py`
```python
# /my_project/urls.py

...
from django.urls import path, include

urlpatterns = [
    ...,
    path('', include('the_app.urls')),
]
```
> Tip: Try to run it by `python manage.py runserver` and go to `localhost:8000` or `127.0.0.1:8000` to see if it's working.

6. Make a Directory for templates
-Make a folder inside `the_app` named `templates`, and inside it, make a folder named `the_app`. That's where we place our templates. If you look at its order, it looks like this:
```
	-/the_app/templates/the_app$

	--OR--

	the_app
		templates
			the_app
	my_project
	manage.py
```

***

## Making a Model
- Django Models are used to make tables and what they should contain (data types like string, picture, etc.). 
-Now we're making a _Person()_ model where every manager can save there the people they were in charge of. And each `Person` has a task so we also gonna make a _Task()_ model.
```python
# /the_app/models.py

from django.db import models
from django.contrib.auth.models import User

# Create your models here.
class Person(models.Model):
	user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
	name = models.CharField(max_length=200, null=True)

	def __str__(self):
		return self.name

class Task(models.Model):
	peep = models.ForeignKey(Person, on_delete=models.CASCADE)
	task = models.CharField(max_length=200, null=True)
	is_complete = models.BooleanField(default=False)

	def __str__(self):
		return self.task

```
-If we look at the first argument of the _Task()_ model we'll see that it takes the _People()_ model as its _ForeignKey_, while on the other hand, the _ForeignKey_ of _People()_ model is the _User()_ model. It means for every `Task` created will be saved to the selected `People` which will be saved on whoseever Manager/`User` is logged in.

***

## Working with the Views
In making a Django app, you can choose what to use between Function-Based Views and Class-Based Views, and we're gonna use the Class-Based Views (CBV). 

### I. UserSignUp() `(FormView)`
The `FormView` class allows you to make new users. We're gonna make a _UserSignup()_ view using the `Formview` class by inheriting it.
```python
# /the_app/views.py

from django.views.generic.edit import FormView
from django.contrib.auth.forms import UserCreationForm

class UserSignup(FormView):
	template_name = 'the_app/signup.html'
	form_class = UserCreationForm
	redirect_authenticated_user = True
```
-This is how you do a simple _UserSignup()_ view. It will work but if you want to add some functionalities for it to add another option to make the job done, it's totally fine to do it.  Now let's add _form_valid()_ for automatic logged in once an account is created.
```python
# /the_app/views.py

class UserSignup(FormView):
	...
	success_url = reverse_lazy('login')

	def form_valid(self, form):
		user = form.save()
		if user is not None:
			login(self.request, user)
		return super(UserSignup, self).form_valid(form)
```
-In Django, you can't name the class functions by random. Instead, they are HTTP calls. When you were using your app with that class, it continues the operation until the requirements you fill in was satisfied, and then it makes a call. If you had a class function with the same name as that call, it will use that function. In our situation here in _UserSignup()_, we just need to provide the information the `form_class` wants. The `form_class` contains the `UserCreationForm` which default asks for a username and password. Once you fulfill them, the `UserCreationForm` calls `form_valid`, and there is where our _form_valid()_ function will be called.

-What we're gonna add this time is a function that disallows you to register a new account while one is still logged in.
```python
# /the_app/views.py

...
from django.shortcuts import redirect

class UserSignup(FormView):
	...

	def form_valid(self, form):
		...

	def get(self, *args, **kwargs):
		if self.request.user.is_authenticated:
			return redirect('personlist')
		return super(UserSignup, self).get(*args, **kwargs)
```
-Our _UserSignup()_ should look like this:
![UserSignup pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yky6utyoqyjnvnwlmtw6.png)

-Now let's make a page for it. Make `signup.html` inside the_app/templates/the_app:
![signup.html pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot7ovugooraqmy96n8lo.png)

-After that, let's make a URL for _UserSignup()_ in urls.py:
```python
# /the_app/urls.py

...
from .views import UserSignup

urlpatterns = [
	path('signup/', UserSignup.as_view(), name='signup'),
]
```

### II. UserLogin (LoginView)
`LoginView` is used for signing in/logging in to the User accounts.
```python
# /the_app/views.py

...
from django.contrib.auth.views import LoginView
from django.urls import reverse_lazy


class UserLogin(LoginView):
	template_name = 'the_app/signin.html'
	fields = '__all__'
	redirect_authenticated_user = True

	def get_success_url(self):
		return reverse_lazy('personlist')
```
-As if you notice, the `get_success_url()` function says `reverse_lazy` to a URL named `'personlist'`. That means when logged in successfully `(get_success_url call)`, head to the `personlist` URL name to access the _PersonList()_ class. For now, we don't have a _PersonList()_ class but we'll make one later.

-We can make a `signin.html` now. Remember to place it inside the `the_app/templates/the_app/`.
![signin.html pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ob8p0z8gcl9v484zt7l.png)

-And don't forget to link it in `urls.py`:
```python
# /the_app/urls.py

...
from .views import UserLogin

urlpatterns = [
	...,
	path('login/', UserLogin.as_view(), name='login'),
]
```

### III. LogoutView
`LogoutView` is all about what you think it does. There is a simple way of making a `LogoutView` using only the `urls.py` by adding some lines of code.
```python
# /the_app/urls.py

...
from django.contrib.auth.views import LogoutView

urlpatterns = [
	...,
	path('logout/', LogoutView.as_view(next_page='login'), name='logout'),
]
```
-_LogoutView.as_view()_ has an argument `next_page` which `'login'` and if we look at the _UserLogin()_ class's URL, we may notice that its name is `'login'`. That means, that after we logged out, our app will automatically redirect to the login page.

### IV. PersonCreate (View)
Django has a built-in class-view for this job which is the `CreateView`. But for some reason like wanting to add some functionalities, I'd rather choose to use the generic `View` function than `CreateView`. `View` is a regular class-based view without any specials to use, making it easier to modify and use if you want a super customizable class view. What is class _PersonCreate()_ do is add an `employee` or a `person` on the User's employee list.
```python
# /the_app/views.py

...
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import View
from django.shortcuts import render,redirect


class PersonCreate(LoginRequiredMixin,View):

	def get(self,request):
		return render(request,'the_app/person_create_form.html')

	def post(self,request):
		user = User.objects.get(username=request.user)
		my_name = request.POST.get('name')
		user.person_set.create(name=my_name)
		my_object = user.person_set.get(name=my_name).id
		return redirect('persondetail',my_object)
```
-If you look at the function names I use in _PersonCreate()_, they look like an ordinary function view. The only difference is their name was also an HTTP call.

-Make a `person_create_form.html` in `the_app/templates/the_app`.
![person_create_form.html pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6giz3ahddj4p5qkejh2.png)

Then the urls:
```python
# /the_app/urls.py

...
from .views import PersonCreate

urlpatterns = [
	...,
	path('person-create/', PersonCreate.as_view(), name='personcreate'),
]
```

### V. PersonList (View)
_PersonList()_ class allows us to see the employee list the User has. Django has a specific class-view for this type of work too, which is called `ListView`. But for the same reason as I have earlier in _PersonCreate()_ class, I use the generic views than the  `ListView`.
```python
# /the_app/views.py

...
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import View
from django.shortcuts import render


class PersonList(LoginRequiredMixin,View):
	def get(self,request):
		account = User.objects.get(username=request.user)
		context = {'person_list':account}
		return render(request,'the_app/home.html',context)
```

-After that, let's make `the_app/templates/the_app/home.html` for this class view:
![home.html pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c12kgm45sh34lmdke23q.png)

-And then, the `theapp/urls.py`:
```python
# /the_app/urls.py

...
from .views import PersonList

urlpatterns = [
	...,
	path('', PersonList.as_view(), name='personlist'),
]
```
-Let's take a look at the `urlpattern` of the _PersonList()_, you may notice that its path name is `personlist`. If we remember, we configure it in _UserLogin()_ before. That means when the _UserLogin()_ is successful, we'll be directed to the _PersonList()_ which allows us to see the listed employees there.

### VI. PersonDetail (DetailView)
Django's ` DetailView()` class allows you to see the details (tasks) of a specific object (person/employee).
```python
# /the_app/views.py

...
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import DetailView


class PersonDetail(LoginRequiredMixin,DetailView):
	model = Person
	context_object_name = 'person'
	template_name = 'the_app/person_detail.html'

	def get(self,*args,**kwargs):
		return super(PersonDetail, self).get(*args,**kwargs)
```
-It will work, but I'm about to add some functions to it for adding, modifying, or deleting tasks for every employee.
```python
# /the_app/views.py

class PersonDetail(LoginRequiredMixin,DetailView):
	...

	def post(self,*args,**kwargs):
		return super(PersonDetail, self).get(*args,**kwargs)

	def dispatch(self,request,pk,*args,**kwargs):
		peep = self.model.objects.get(id=pk)
		if self.request.POST.get('save'):
			for task in peep.task_set.all():
				if request.POST.get(f't{task.id}') == 'clicked':
					task.is_complete = True
				else:
					task.is_complete = False
				task.save()
		
		elif self.request.POST.get('add_item'):
			new = request.POST.get('new_item')
			peep.task_set.create(task=new, is_complete=False)
		
		elif request.POST.get('delete_this'):
			task_index = request.POST.get('delete_this')
			peep.task_set.get(id=task_index).delete()

		return super(PersonDetail, self).dispatch(request,*args,**kwargs)
```
-The `dispatch` call allows us to add or modify a `Person`'s task. Look closely at _dispatch()_ functions if-statements, those if-statements are the actions we can do on _PersonDetail_'s page on run. If we make any of those actions, it calls the _post()_ and now we can access the data/changes the User made using _dispatch()_ and saving them.

-The _PersonDetail()_ code looks exactly as this one:
![PersonDetail pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bcl7pbavqr80mg09gr27.png)

-And for our template, let's make `person_detail.html`.
![person_detail.html pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gxy791sw57nq77h0l3b.png)

-Don't forget the `urls.py`:
```python
# /the_app.urls.py
...
from .views import PersonDetail

urlpatterns = [
	...,
	path('person-detail/pk=<int:pk>', PersonDetail.as_view(), name='persondetail'),
]
```

### VII. PersonUpdate (UpdateView)
```python
# /the_app/views.py

...
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import UpdateView

class PersonUpdate(LoginRequiredMixin,UpdateView):
	model = Person
	fields = ['name']
	template_name = 'the_app/person_update_form.html'
	success_url = reverse_lazy('personlist')
```
-This type of class-view updates the field you chosen.

-For `the_app/person_update_form.html`:
![person_update_form.html pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/up0i5jm1rtojoowyubph.png)

-For urls:
```python
# /the_app/urls.py

...
from .views import PersonUpdate

urlpatterns = [
	...,
	path('person-update/pk=<int:pk>', PersonUpdate.as_view(), name='personupdate'),
]
```

### VIII. PersonDelete (DeleteView)
`DeleteView` does what exactly it sounds; it delete objects (person/employees.
```python
# /the_app/views.py

...
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import DeleteView


class PersonDelete(LoginRequiredMixin,DeleteView):
	model = Person
	success_url = reverse_lazy('personlist')
	template_name = 'the_app/person_confirm_delete.html'
```
-For `person_confirm_delete.html`:
![person_confirm_delete.html pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tiwp9jinxd3qdakgbov.png)

-For urls of _PersonDelete()_:
```python
# /the_app/urls.py

...
from .views import PersonDelete

urlpatterns = [
	...
	path('person-delete/pk=<int:pk>', PersonDelete.as_view(), name='persondelete'),
]
```

***

And now, it's done!! you can try it by running `python manage.py runserver` in CLI and then open `127.0.0.1:8000` or `localhost:8000` to see if it's working.

***
Some screenshots of the app:
Login
![login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6v0qwz13fxvltg1nev0.png)

Signup
![signup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15ivte3ze6w1gtc53v8e.png)

Home
![home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/108ckcjdvfnx1ket7ehy.png)

PersonCreate
![personcreate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arqpjyfx0oqwlh9edo0c.png)

PersonDetail
![persondetail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pouode8iv245ekhp4qy3.png)

Click [here](https://github.com/Codeit-Michael/About-CBV) to see the source code.

Good day, guys!! You can reach my account for more:
Twitter: [`Codeit_Michael`](https://twitter.com/Codeit_Michael)
Github: [`Codeit-Michael`](https://github.com/Codeit-Michael)