To practice the essensial functionalities of Django, we will be building a dummy twitter application (hence Dwitter). For now we will focus on modeling user profiles and authentication.
This week we will be building the following features:
- User login, logout, and password reset
- User registration
- Landing page (home page)
- User management admin page
Our final product will look like this:
- Landing page: Because we are only focusing on user management this week, the landing page will be a simple page that displays the current user's username. If the user is not logged in, the page will not open, and be automatically redirected to the login page. Next week we will add a list of tweets to the landing page.
- Login page: The login page will have a form that takes in the user's username and password. If the user is not registered, the page will display an error message. If the user is registered, the page will redirect the user to the originally intended page (or the landing page as default). Here we also have a link to the password reset page, and a link to the registration page.
After loggin in, users username will be displayed on the navigation bar, beside the logout button. We will implement the profile page (which will be displayed when the user clicks on their username on the navigation bar) next week. If the user logs out, the navigation bar will revert to the original state and user will see a page that says "You logged out".
- Registration page: The registration page will have a form that takes in the user's basic information. In case there are no errors (e.g. username already exists), the page will redirect the user to the login page.
- Password reset page: The password reset page will have a form that takes in the user's email. Regardless of whether the email is registered or not, the page will display a success message stating that "If there exist an account associated with your email, we have sent you instructions on how to recover your password". If the email is actually registered, after the user submits the form, we will send them an email in the background, containing a link to the password reset confirmation page.
We will only print the email to the console for now, and it will look like this:
After clicking on the link, the user will be redirected to the password reset confirmation page, where they can enter their new password. After submitting the form, the user will be redirected to the login page.
And if an incorrect link (or previously used) is clicked, an error message will be displayed instead of the password reset confirmation page.
You can either use the boilerplate code provided in this repository or start from scratch.
Install Django and create a new project:
pip install django
django-admin startproject dwitter
Create two new applications tweets
and accounts
, and add them to the INSTALLED_APPS
in settings.py. You can either follow the structure of this repository and have your apps in a folder called "apps", or you can just put them in the root of your project.
tweets
will be responsible for handling tweets, and accounts
will be responsible for handling user profiles, social interactions and authentication.
In this session we will be focusing on the accounts
app.
After setting up the project, first you need to tell django to create the database. Django uses migrations to keep track of changes to the database, by running the following commands, django will look out for changes in the data models (in models.py
of INSTALLED_APPS
) and apply them to the database, which in this case will create a brand new sqlite database.
python manage.py makemigrations # creates migrations for the apps in INSTALLED_APPS
python manage.py migrate # creates the database (or updates it if it already exists)
Then you can run the server:
python manage.py runserver
Django provides a default user model (with essensial data fields that we need for this project). We can also extend this model to add more fields to it in the future.
You can find the default user model in django.contrib.auth.models.User
, it is usually suggested to extend this model instead of creating a new one. Because of this, it is usually suggested not to import the model directly, but instead use django.contrib.auth.models.get_user_model()
to get the instance of the user model that is currently active in this project.
As a side note, In case you needed to create a new model for a future project, you can find more information here.
Django provides a built-in authentication system that handles user login, logout, and password change. It also provides a set of forms and views that you can use to setup your website's authentication system (check out here for more information). For our project these default abstractions cover everything we are looking for. Therefore, we won't reinvent the wheel and implement Login, Logout and Password Change using Django built-in functionalities.
In order to use the authentication system, you need to add django.contrib.auth
to your INSTALLED_APPS
setting (which is usually there by default), and include the django.contrib.auth.urls
URLconf in your project urls.py
file to make use of its built-in views.
To add a set of urls into your project, you can use the django.urls.include
function without the need for explicitly importing your intended url module. So your urls.py
file should look something like this:
# dwitter/urls.py
from django.urls import include
urlpatterns = [
path("accounts/", include("django.contrib.auth.urls")), # ADDITION: include the default auth urls
]
This will include the following URL patterns:
accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']
But if you visit any of these URLs (after running the development server), you'll get a TemplateDoesNotExist
error. This is because, although Django provides these views, we are in charge of providing the templates that will be used to render them. In order to do that, we need to create the following templates in templates/registration
folder (and we also need to add templates
to the DIRS
list in TEMPLATES
in settings.py
):
login.html
: The login page. Context has aform
variable that you can use to render the login form.logout.html
: The logout page. (Just a message saying "You have been logged out." and a link to the login page should suffice.)password_reset_form.html
: The password reset page. Context has aform
variable that you can use to render the password reset form.password_reset_email.html
: The email template for the password reset email. Context hasprotocol
domain
uid
token
which you can use to construct the password reset link.password_reset_done.html
: The password reset done page. (Just a message saying "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." and a link to the login page should suffice.)password_reset_confirm.html
: The password reset confirmation page. Context has aform
variable that you can use to render the password reset confirmation form (which asks for the new password).password_reset_complete.html
: The password reset complete page. (Just a message saying "Your password has been set. You may go ahead and log in now." and a link to the login page should suffice.)
By providing these templates, we will have implemented the Login, Logout and Password Change features.
Django template has tons of useful tricks and features which makes it easy to create website that follow a base structure. For example, you can define a base overall template for your website (which defines the location of your navigation bar, content and etc.) and use the {% extends "base.html" %}
tag to extend your base template, and providing {% block content %}
tag to define a block that will be replaced by the content of the child template. You can find more information about Django templates here.
A general Bootstrap 5 enabled template is provided in templates/base.html
file. You can use it as a base template for your website. This template assumes that your child template will provide a page_content
block, which will be rendered in the main content area of the website.
Navigation bar is a common feature in most websites. In order to make it easier to implement, we have provided a navigation bar template in templates/nav.html
, and included it in the base template. You can use this template in your child templates by using the {% include "nav.html" %}
tag. To complete the navigation bar implementation, fill in the black spots in the nav.html
template, so that when a user is logged in, the navigation bar will show the user's username and a link to logout API, and when a user is not logged in, the navigation bar will show a link to the login page.
In order to check if a user is logged in, you can use the user.is_authenticated
variable in your Django templates. The user
context variable will have the information of the logged-in user such as its username
as well (user.username
) For example, you can use the following code to show the user's username and a link to logout API if the user is logged in, and show a link to the login page if the user is not logged in:
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">{{ user.username }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
{% endif %}
If you look closely, you will notice that, we need to implement at least three pages for a form
context and the rest showing some message. So we can create a base template for forms and showing messages, and extend it in our templates. This will make our code more readable and easier to maintain.
In order to do that, you can create a templates/generic_form.html
file that renders a form page given a context with form
and action
variables. You can use the following code as a starting point:
{% extends "base.html" %}
{% block page_content %}
<!-- block for form header to extend in other templates-->
{% block form_header %} {% endblock %}
<form method="post" action="{{ action }}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endblock %}
Althogh this template suffices for rendering any form, you can add bootstrap classes to the form elements to make it look better. You can find more information about bootstrap forms here. To add boostrap to your form, instead of relying on the default Django form rendering ({{form}}
or {{form.as_p}}
), you can iterate over the form fields and render them as you wish.
There is also a plathera of 3rd party libraries that can help you with rendering forms. For example, you can use django-crispy-forms to render your forms in a more elegant way. You can also use django-widget-tweaks to add custom classes to your form elements.
You can also create a templates/generic_message.html
file that renders a content (either in the context or as a block). You can use the following code as a starting point:
{% extends "base.html" %}
{% block page_content %}
{% if content %}
<p>{{ content }}</p>
{% else %}
{% block content %} {% endblock %}
{% endif %}
{% endblock %}
This template will render the content of the content
variable in the context, or the content of the content
block if the content
variable is not provided (therefore expects to be extended by a child template that provides a content block instead).
-
Log in: Either make your own form template or use your generic_form template to create the login page template in
templates/registration/login.html
. To add links to other pages, you can use the{% url %}
template tag. For example, to link to the password reset page, you can use{% url 'password_change' %}
. You can find more information about the{% url %}
template tag here. -
Log out: Create the logout page template in
templates/registration/logout.html
either by extending your generic_message template or from scratch. You can use the{% url %}
template tag to link to the login page (API namelogin
). -
Password Reset Form: Similar to the login page, create the password reset form page template in
templates/registration/password_reset_form.html
either by extending your generic_form template or from scratch. -
Password Reset Email: Create the password reset email template in
templates/registration/password_reset_email.html
using the mentioned context variables. The password reset link will be of the form<protocol>://<domain>/<password_reset_confirm url>/<uidb64>/<token>/
. You can use the{% url %}
template tag to create the link. For example, to create the link to the password reset confirmation page, you can use{% url 'password_reset_confirm' uidb64=uid token=token %}
. Therefore in your template, you can use the following code to create the link:... {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} ...
-
Password Reset Done: Create the password reset done page template in
templates/registration/password_reset_done.html
either by extending your generic_message template or from scratch. -
Password Reset Confirm: Create the password reset confirmation page template in
templates/registration/password_reset_confirm.html
either by extending your generic_form template or from scratch. In addition to theform
andaction
context variables, avalidlink
context variable is also provided. You can use this variable to show a message if the link is not valid, or to show the form if the link is valid. -
Password Reset Complete: Create the password reset complete page template in
templates/registration/password_reset_complete.html
either by extending your generic_message template or from scratch.
-
Django provides a
UserCreationForm
class that can be used to create a user. You can find more information about this class here. We wish for our signup page to also gather the user's first name and last name and email, which are not provided by theUserCreationForm
class. Therefore, we will create a new form class that inherits from theUserCreationForm
class and adds the required fields inaccounts/forms.py
. -
Now that we have a form class, we need to create a view that renders the form and handles the form submission. Create a
signup
view inaccounts/views.py
that renders the form and handles the form submission. You can usedjango.views.generic.edit.FormView
which does this automatically for you. You can find more information about this class here.If you are using the
FormView
class, you can just provide thegeneric_form.html
that you implemented in the previous part as thetemplate_name
attribute. Remember that we want to show the login page after the user signs up, so you can set thesuccess_url
attribute to redirect the user to the login page after the form is submitted successfully. To avoid hardcoding the login page URL, you can use thedjango.urls.reverse_lazy
function to get the URL of the login page. You can find more information about this function here. -
Finally, we need to create a URL pattern for the signup page. Create a
signup
URL pattern in thedwitter/urls.py
that maps thesignup
view to thesignup
URL. You can also name this viewsignup
so that you can easily use the{% url %}
template tag to link to the signup page in the login page template.
- For now just create a view in
tweets/views.py
that greets the user in case they are logged in, otherwise redirects them to the login page. In case you wish to implement this as a function-based view, you can use thedjango.contrib.auth.decorators.login_required
decorator, to handle the redirection. You can find more information about this decorator here. - Link your view to the root URL pattern in
dwitter/urls.py
so that the user is redirected to the landing page when they visit the root URL ("").
We don't do much this week with the admin page, but we will use it in the future. Therefore, we will just set it up now.
-
Create a superuser account by running
python manage.py createsuperuser
. You can use this account to log in to the admin page. -
Customize the admin pages' header and name by adding the following code to one of
accounts
ortweets
apps'admin.py
file:from django.contrib import admin admin.site.name = "Dwitter Admin" admin.site.site_header = "Dwitter" admin.site.site_title = "Dwitter Admin Portal" admin.site.index_title = "Welcome to Dwitter Admin Portal"
-
Because we won't be using Django's groups, we will remove the groups and permissions from the admin page. To do this, add the following code
account/admin.py
file:from django.contrib import admin from django.contrib.auth.models import Group admin.site.unregister(Group)
Here is a bunch of the stuff that we brought up during the tutorial session. A bunch of these might answer some of the common questions that you might have.
- Windows: If you are on Windows, a pretty common problem that you might run into is not having your python properly setup. Before anything, make sure you have python3 installed, and make sure your PATH is set up properly so that when you install packages they are both accessable from your command shell, and python shell. This means that you can run
python
in your command prompt and also when you install a package usingpip
that externally callable commands, you are able to execute them from your shell. Django itself is a good example of a 3rd party package with externally callable commands (e.g.django-admin
). Check everything by runningdjango-admin --version
. If it doesn't work, you might have to do some googling to figure out how to set up your PATH properly, before proceeding with the rest of your projects. - PyCharm: If you are using PyCharm, keep in mind that PyCharm introduces a new python virtual envioronment for each of your projects by default. Therefore, you have to install all of your packages in each of your projects, even if you have already installed them in another project. To work with your project's virtualenv interpreter, either use PyCharm's GUI tools (check here for Django specific PyCharm GUI wizards), or just tap into your project's virtualenv by opening up a new PyCharm's terminal.PyCharm automatically activates the virtual environment for the project that you are currently working on.
- Visiting your website: When viewing HTML files (django-template files included), some IDEs show a pop op suggestion to view it in the browser; however, this is not the same as visiting your website, and you'll just see the interpretation of your browser of your template files. If you wish to visit your website, you have to run your django server and visit the address that your server is listening on, (by default, it is
http://localhost:8000/
).
It's good to have a train of thought when creating a django (or any other) project. Here is how I went about creating this project:
-
Decided on the features that I wished to implement for my twitter clone. In this case, I wanted to implement a simple twitter clone with the following features:
- Users can create an account, login, and logout, and recover their password.
- Users have to be logged in to use the website.
- Users can view and create tweets.
- In the simplest version, the home page will show all tweets (no need to implement following).
-
Focusing on User accounts for this week, I started by creating a django project (
django-admin startproject dwitter
), and then created a django app (python manage.py startapp accounts
, although in this case I packed my apps into a directory calledapps
, and manually changed my app.name inaccounts/app.py
todwitter.apps.accounts
). Added the app (dwitter.apps.accounts
) to myINSTALLED_APPS
insettings.py
. -
We want to handle user accounts, meaning we need to make an abstraction of a user and its features in our Database of choice. We know that we can do this by creating our own model, but because user abstraction is common among basically all websites, why not use other peoples codes for it? A simple googling of "django user model" will lead you to django's built-in implementation of such an abstraction (Django's Official Documentation, Geeks4Geeks). Ok, this looks decent, now that we have a data abstraction, what is our website supposed to do? Meaning what is the logic that we seek to implement? Login, logout, Password recovery, A.K.A. an authentication system! Again, googling "Django authentication system" will lead you to Using the Django authentication system's official docs. Scrolling through this page, you will see that Django has already implemented all the logic that we want to implement. Ok how should we connect this to our naked dwitter project?
-
A website's logic over its data is reachable from its user-interface, in most cases its front-end, and in technical terms, its exposed APIs which django calls views. So, we need to create django views (class-based/function based/ etc.) for our authentication system? Looking up "Authentication Views" in Using the Django authentication system's official docs, we see that Django has already implemented all the views that we need (here). Ok, so we just need to connect our views to our urls. The rest is covered in the tutorial (adding the urls to our project, coding up the templates). If you do a little bit more of googling you'll come across this very neat tutorial that covers the exact same thing: Django Authentication System Tutorial. I highly recommend you to go through this tutorial, as it covers a lot of the stuff that we covered in the tutorial, but in a more detailed manner. It also covers a lot of the stuff that we didn't cover in the tutorial, such as how to use django's built-in admin panel to manage your users.
-
Now that we have login,logout, and password recovery, we have to implement the signup page. (see the comments in
accounts/views.py
andaccounts/forms.py
for more details). After implementing the signup view, we'll add it to our urls, and then we'll add a link to it in our login page (inaccounts/templates/registration/login.html
). -
To finish things off, we'll just create a view for our home page (in
dwitter/tweets/views.py
), and add it to our urls (indwitter/urls.py
).