Our social messaging app is basically done at this point but there’s a big, big issue we need to fix. Right now, anyone can visit any page. That means if a random user without an account knows the URL for creating a new message they can add messages! Try this for yourself. Click on the “logout” link in the dropdown list on the nav bar under your username.

Nav logout link

Once logged out, go to http://127.0.0.1:8000/posts/new/, add text for a message, and click “Save.” You’ll see an error.

Create page error

This is because our model expects an author field which is linked to the current logged-in user. Since there’s no author, the submission fails.

Even if we fixed this particular error, the fact that a non-loggedin user can view the entire site poses additional security challenges. What if a malicious person wrote a program to submit a million different message posts and comments? If you require a user account, you can simply block this person. But with no user account required, there are limited ways to prevent such an attack.

This general problem is known as authorization. It’s common to set different rules around who is authorized to view areas of your site. Note that this is different than authentication which is the process of registering and logging-in users. Authorization restricts access; authentication enables a user signup and login flow.

As a mature web framework, Django has built-in functionality for authorization that we can quickly use. Throughout the rest of this chapter we’ll limit access to various pages only to logged-in users. We’ll also add our tests and deploy the site to Heroku.

Mixins

A mixin is a special kind of multiple inheritance that Django uses to both avoid duplicate code and allow for customization. For example, the built-in generic ListView needs a way to return a template. But so does DetailView and in fact almost every other view. Rather than repeat the same code in each big generic view, Django breaks out this functionality into a “mixin” known as TemplateResponseMixin. Both ListView and DetailView use this mixin to render the proper template.

If you read the Django source code, which is freely available on Github, you’ll see mixins used all over the place.

To restrict view access to only logged in users, Django has a LoginRequired mixin that we can use. It’s powerful and extremely concise.

Within the existing posts/views.py file import it at the top and then add LoginRequiredMixin to our PostCreateView. Make sure that the mixin is to the left of ListView so it will be read first. We want the ListView to already know we intend to restrict access.

And that’s it! We’re done.

# posts/views.py
from django.contrib.auth.mixins import LoginRequiredMixin # new
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy

from . import models


class PostCreateView(LoginRequiredMixin, CreateView):
    model = models.Post
    template_name = 'post_new.html'
    fields = ['message']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)
...

Now return to the URL for creating new messages at http://127.0.0.1:8000/posts/new/ and you’ll see the following error:

Error page

What’s happening? Django has automatically redirected us to the default location for the login page which is at /accounts/login however if you recall, in our project-level URLs we are using users/ as our route. That’s why our login page is at users/login. So how do we tell our PostCreateView about this?

If you look at the documation for LoginRequired mixin it tells us the answer. We can add a login_url to override the default parameter. We’re using the named URL of our login route here login.

# posts/views.py
class PostCreateView(LoginRequiredMixin, CreateView):
    model = models.Post
    template_name = 'post_new.html'
    fields = ['message']
    login_url = 'login' # new

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

Try the link for creating new messages again: http://127.0.0.1:8000/posts/new/.

Login page

It now redirects users to the login page. Just as we desired!

Updating views

Now we see that restricting view access is just a matter of adding LoginRequiredMixin at the beginning of all existing views and specifying the correct login_url. Let’s update the rest of our posts views since we don’t want a user to be able to create, read, update, or delete a message if they aren’t logged in.

The complete views.py file should now look like this:

# posts/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy

from . import models


class PostListView(LoginRequiredMixin, ListView): # new
    model = models.Post
    template_name = 'post_list.html'
    login_url = 'login' # new


class PostCreateView(LoginRequiredMixin, CreateView): # new
    model = models.Post
    template_name = 'post_new.html'
    fields = ['message']
    login_url = 'login' # new

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)


class PostDetailView(LoginRequiredMixin, DetailView): # new
    model = models.Post
    template_name = 'post_detail.html'
    login_url = 'login' # new


class PostUpdateView(LoginRequiredMixin, UpdateView): # new
    model = models.Post
    fields = ['message']
    template_name = 'post_edit.html'
    login_url = 'login' # new


class PostDeleteView(LoginRequiredMixin, DeleteView): # new
    model = models.Post
    template_name = 'post_delete.html'
    success_url = reverse_lazy('posts')
    login_url = 'login' # new

Go ahead and play around with the site to confirm that the login redirects now work as expected. If you need help recalling what the proper URLs are, log in first and write down the URLs for each of the CRUD functionality.

Tests & Deployment

Author’s note: I’m still working on the text for the tests and deployment instructions for this app. Otherwise it’s done though and you can proceed to the conclusion.