Refactor the Login View to a Class-Based View
Next, you will recreate the functionality from the last chapter using class-based views.
What are class-based views?
As you may have guessed, class-based views represent views via classes rather than functions.
In essence, class-based views split different functionalities of a view, for example, how it responds to GET
vs. POST
requests into different methods.
Let's see how to convert the login_page
function-based view to a class-based view.
Step 1: Refactor the Function-Based Login View to a Class-Based View
First, let's look at the current login view.
def login_page(request):
form = forms.LoginForm()
message = ''
if request.method == 'POST':
form = forms.LoginForm(request.POST)
if form.is_valid():
user = authenticate(
username=form.cleaned_data['username'],
password=form.cleaned_data['password'],
)
if user is not None:
login(request, user)
return redirect('home')
message = 'Login failed!'
return render(request, 'authentication/login.html', context={'form': form, 'message': message})
At the moment, this function executes regardless of the request method. Only the if request.method == POST
statement is exclusive to POST
requests.
Now let's look at it as a class-based view:
from django.views.generic import View
class LoginPageView(View):
template_name = 'authentication/login.html'
form_class = forms.LoginForm
def get(self, request):
form = self.form_class()
message = ''
return render(request, self.template_name, context={'form': form, 'message': message})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
user = authenticate(
username=form.cleaned_data['username'],
password=form.cleaned_data['password'],
)
if user is not None:
login(request, user)
return redirect('home')
message = 'Login failed!'
return render(request, self.template_name, context={'form': form, 'message': message})
As you can see, the view logic is now nicely split into get
and post
methods. We have also taken some reused elements and set them as class attributes. The advantage of this is two-fold: it allows you to easily change these attributes from one place, making the code more maintainable, and it allows one to inherit from this class and change the template and form, making the class flexible and extensible!
Step 2: Add the Class-Based View as a URL Pattern
To include a class-based view in a URL pattern, you need to use the method as_view()
. This is one of the utility methods that you have access to due to inheriting from django.views.View
.
# fotoblog/urls.py
urlpatterns = [
...
path('', authentication.views.LoginPageView.as_view(), name='login'),
...
]
You may find when making class-based views that you want to restrict the view to logged in users on your site. The easiest way to do this is to make your view inherit from the django.contrib.auth.mixins.LoginRequiredMixin
, as well as the base View
.
And it's as simple as that! If you have decided to code along with this section, try out the login functionality now.
Implement Login With Generic Views
Class-based views came about as a way to create more easily extensible and flexible generic views. The point of generic views is to reduce the amount of code commonly repeated between different views.
For example, if you wanted a view displaying information on a specific instance of a model, you might use the DetailView
, or if you wanted to list all the instances of a model, the ListView
.
As you may have guessed, you can also use these generic views to handle authentication. The previous chapters gave you insight into some of the low-level workings of authentication. In another project, you may find the ease and simplicity of the generic authentication views a better fit.
Let's rebuild our authentication system using generic views.
We'll use the LoginView
generic view located in django.auth.views
. When using the LoginView
, all of the view logic is handled, so you just need to build a template and make sure some configuration is in place.
The key steps to recreate the login functionality with the LoginView
are:
Implement a template that includes a form with the variable
form
.Pass this template to the
LoginView.as_view()
method via the argumenttemplate_name
.Set the login page to automatically redirect logged in users by passing
LoginView.as_view()
the keyword argumentredirect_authenticated_user=True
.Set
LOGIN_REDIRECT_URL
in the settings to the success redirect page.
It looks like the template you are already using for your login page will work nicely!
Let’s carry out these steps to implement LoginView
now.
Step 1: Configure the Settings
Add LOGIN_REDIRECT_URL
to the settings.
# fotoblog/settings.py
LOGIN_REDIRECT_URL = 'home'
Step 2: Add the URL Pattern and the View Configuration
As all of the view logic is already handled by the generic LoginView
, you don't need to implement the view in views.py
. Just add it straight into urls.py
with some configuration.
# authentication/urls.py
from django.contrib.auth.views import LoginView
urlpatterns = [
path('', LoginView.as_view(
template_name='authentication/login.html',
redirect_authenticated_user=True),
name='login'),
path('logout/', authentication.views.logout_user, name='logout'),
]
And that's it! You can even deleteforms.py
file as we are no longer using LoginForm
.
Try testing out the login functionality now. You should find that it’s working as it was before.
Now that you've seen how to implement a generic view; it's time to implement some yourself.
Exercise: Implement the Generic Authentication Views
The LoginView
is not the only authentication view that Django provides; there is a whole suite of generic views.
For this exercise, you should implement the following views found in django.contrib.auth.views
:
LogoutView
- you can delete thelogout_user
view we built once this is done.PasswordChangeView
PasswordChangeDoneView
Try it out!
Once you finish, or if you get stuck, you can see the correct implementation by checking out the the solution on GitHub.
Compare Class-Based Views and Function-Based Views
Wow, that was a lot to cover! Remember, there are three main approaches for implementing views:
Function-based views - the request is handled by one function, with different request types such as
POST
andGET
managed by conditional statements.Class-based views subclassing
django.views.generic.View
- the request is handled by a class, with different request types being managed by separate methods.Generic class-based views - the request is handled by a generic class-based view, which allows you to avoid writing commonly-used view logic.
Which approach should I use, and when?
Here’s the bad news, there is no definitive answer! It all comes down to preference.
Daniel and Audrey Feldroys’ book Two Scoops of Django 3.x (an excellent resource for Django best practices) gives a good explanation of the schools of thought surrounding views. Here's a summary:
While the type of view to use often comes down to personal preference, there are generally three approaches that developers adhere to:
Use generic views whenever and wherever you can, otherwise subclass
django.views.generic.View
. It reduces your workload at the cost of readability.Use class-based views but only subclassing
django.views.generic.View
. This approach is useful if you prefer the syntax of class-based views but want to avoid the readability problems of using somewhat opaque generic views.Only use function-based views as they are arguably easier to read and understand. Use class-based views if you share large chunks of code between views, in which case you should create a base class-based view and then subclass it.
An exception to this is that the generic authentication views are often used in projects that primarily use a function-based view approach, as these provide a real time-saver at the start of a project.
For the rest of this course, I will use function-based views to build out the site, but feel free to try to implement these views as class-based views or even generic views if that is what you prefer.
Let's Recap!
Class-based views are an alternative approach to function-based views. Both are equally valid.
Generic views offer many shortcuts that help reduce the drudgery of repetitive tasks.
Django's authentication views are helpful generic views and can be used to avoid low-level authentication handling.
Now that we have implemented the authentication views, let's build the site's signup functionality.