Welcome to the 5th part of the tutorial series! In this tutorial, we are going to learn more about protecting views
against unauthorized users and how to access the authenticated user in the views and forms. We are also going to
implement the topic posts listing view and the reply view. Finally, we are going to explore some features of Django
ORM and have a brief introduction to migrations.
Protecting Views
We have to start protecting our views against non-authorized users. So far we have the following view to start new
posts:
In the picture above the user is not logged in, and even though they can see the page and the form.
Django has a built-in view decorator to avoid that issue:
Then if we try to log in now, the application will direct us back to where we were.
So the next parameter is part of a built-in functionality.
Login Required Tests
Let’s now add a test case to make sure this view is protected by the @login_required decorator. But first, let’s do
some refactoring in the boards/tests/test_views.py file.
In the test case above we are trying to make a request to the new topic view without being authenticated. The
expected result is for the request be redirected to the login view.
Accessing the Authenticated User
Now we can improve the new_topic view and this time set the proper user, instead of just querying the database
and picking the first user. That code was temporary because we had no way to authenticate the user. But now we can do
better:
Observe that now we are dealing with two keyword arguments: pk which is used to identify the Board, and now we have
the topic_pk which is used to identify which topic to retrieve from the database.
Note that we are indirectly retrieving the current board. Remember that the topic model is related to the board model,
so we can access the current board. You will see in the next snippet:
Observe that now instead of using board.name in the template, we are navigating through the topic properties, using
topic.board.name.
Now let’s create a new test file for the topic_posts view:
boards/tests/test_view_topic_posts.py
Note that the test setup is starting to get more complex. We can create mixins or an abstract class to reuse the code
as needed. We can also use a third party library to setup some test data, to reduce the boilerplate code.
Also, by now we already have a significant amount of tests, and it’s gradually starting to run slower. We can instruct
the test suite just to run tests from a given app:
We could also run only a specific test file:
Or just a specific test case:
Cool, right?
Let’s keep moving forward.
Inside the topic_posts.html, we can create a for loop iterating over the topic posts:
templates/topic_posts.html
Since right now we don’t have a way to upload a user picture, let’s just have an empty image.
I downloaded a free image from IconFinder
and saved in the static/img folder of the project.
We still haven’t really explored Django’s ORM, but the code {{post.created_by.posts.count}}
is executing a select count in the database. Even though the result is correct, it is a bad approach. Right now it’s
causing several unnecessary queries in the database. But hey, don’t worry about that right now. Let’s focus on how
we interact with the application. Later on, we are going to improve this code, and how to diagnose heavy queries.
Another interesting point here is that we are testing if the current post belongs to the authenticated user:
{%ifpost.created_by==user%}. And we are only showing the edit button for the owner of the
post.
Since we now have the URL route to the topic posts listing, update the topics.html template with the link:
Also take the time to update the return redirect of the new_topic view function (marked with the comment
# TODO).
Very important: in the view reply_topic we are using topic_pk because we are referring to the keyword argument
of the function, in the view new_topic we are using topic.pk because a topic is an object (Topic model
instance) and .pk we are accessing the pk property of the Topic model instance. Small detail, big difference.
The first version of our template:
templates/reply_topic.html
Then after posting a reply, the user is redirected back to the topic posts:
We could now change the starter post, so to give it more emphasis in the page:
The essence here is the custom test case class ReplyTopicTestCase. Then all the four classes will extend this
test case.
First, we test if the view is protected with the @login_required decorator, then check the HTML inputs, status code.
Finally, we test a valid and an invalid form submission.
QuerySets
Let’s take the time now to explore some of the models’ API functionalities a little bit. First, let’s improve the
home view:
We have three tasks here:
Display the posts count of the board;
Display the topics count of the board;
Display the last user who posted something and the date and time.
Let’s play with the Python terminal first, before we jump into the implementation.
Since we are going to try things out in the Python terminal, it’s a good idea to define a __str__ method for all
our models.
In the Post model we are using the Truncator utility class. It’s a convenient way to truncate long strings into
an arbitrary string size (here we are using 30).
Now let’s open the Python shell terminal:
The easiest of the three tasks is to get the current topics count, because the Topic and Board are directly related:
That’s about it.
Now the number of posts within a board is a little bit trickier because Post is not directly related to Board.
Here we have 11 posts. But not all of them belongs to the “Django” board.
Here is how we can filter it:
The double underscores topic__board is used to navigate through the models’ relationships. Under the hoods, Django
builds the bridge between the Board - Topic - Post, and build a SQL query to retrieve just the posts that belong to
a specific board.
Now our last mission is to identify the last post.
Observe that we are using self, because this method will be used by a Board instance. So that means we are using
this instance to filter the QuerySet.
Now we can improve the home HTML template to display this brand new information:
templates/home.html
And that’s the result for now:
Run the tests:
It seems like we have a problem with our implementation here. The application is crashing if there are no posts.
templates/home.html
Run the tests again:
I added a new board with no messages just to check the “empty message”:
Now it’s time to improve the topics listing view.
I will show you another way to include the count, this time to the number of replies, in a more effective way.
As usual, let’s try first with the Python shell:
Here we are using the annotate QuerySet method to generate a new “column” on the fly. This new column, which will
be translated into a property, accessible via topic.replies contain the count of posts a given topic has.
We can do just a minor fix because the replies should not consider the starter topic (which is also a Post instance).
Next step now is to fix the views count. But for that, we will need to create a new field.
Migrations
Migration is a fundamental part of Web development with Django. It’s how we evolve our application’s models keeping
the models’ files synchronized with the database.
When we first run the command python manage.py migrate Django grab all migration files and generate the database
schema.
When Django applies a migration, it has a special table called django_migrations. In this table, Django registers
all the applied migrations.
So if we try to run the command again:
Django will know there’s nothing to do.
Let’s create a migration by adding a new field to the Topic model:
Now open a topic and refresh the page a few times, and see if it’s counting the page views:
Conclusions
In this tutorial, we made some progress in the development of the Web boards functionalities. There are a few things
left to implement: the edit post view, the “my account” view for the user to update their names, etc. After those two
views, we are going to enable markdown in the posts and implement pagination in both topic listing and topic replies
listing.
The next tutorial will be focused on using class-based views to solve those problems. And after that, we are going to
learn how to deploy our application to a Web server.
I hope you enjoyed the fifth part of this tutorial series! The sixth part is coming out next week, on Oct 9, 2017.
If you would like to get notified when the fifth part is out, you can subscribe to our mailing list.
The source code of the project is available on GitHub. The current state of the project can be found under the release
tag v0.5-lw. The link below will take you to the right place: