Write powerful, clean and maintainable JavaScript.

RRP $11.95
Django is the most-used Python framework for web development. Its built-in features and robust structure make it an excellent option when building web applications. But there are so many resources out there that it’s sometimes overwhelming to apply that knowledge to real-world projects. In this tutorial, we’re going to build a full-stack web application, using Django on the back end and Django Templates stylized with Bootstrap on the front end.
To get the most out of this tutorial, you’d ideally have a grasp of the following:
If you don’t have previous experience with Django, don’t be afraid of continuing with this tutorial. This will be a step-by-step process, and each step will be explained.
Before starting, I want to introduce you to your new best ally, the Django documentation. We’ll be referencing it throughout the article, so make sure to get acquainted with it.
All the source code of this tutorial is available on this GitHub repo.
The complexity of a project depends on all the features we want to include. The more features we want to offer to users, the more time we’ll need to spend building and integrating everything into a unique project.
Taking that into account, we’re going to see a quick distinction between what we’re going to build and what we’re not.
In this tutorial, we’ll build a full-stack (back-end and front-end development) photo-sharing app. Our app will include the following features:
Note: although this app seems quite similar to a social network, it isn’t one. An app like Instagram or Twitter has a lot of complexity that can’t be covered in a single article.
Let’s define the technologies we’re going to use. We’ll cover the installation process of each one when we need to use it.
On the back end, Django will be the core framework of the app. It allows us to define the URLs, define the logic, manage user authentication, and control all the database operations through the Django ORM (object-relational mapper).
Also, we’ll be using a couple of third-party packages to accelerate the development of some features.
Django-taggit provides us the ability to set up a simple tag system in few steps. Pillow is a Python package that provides Django image manipulation capabilities. Finally, Django-crispy-forms gives us a simple way to display Bootstrap forms.
On the front end, we’re going to use the Django template language, which consists of HTML files that display data dynamically.

We’ll also be using Bootstrap 5 (the latest version at the time of writing) for the design of the site.
Note: you can always check the dependencies used in this project in the requirements.txt file.
Let’s start with Django!
First of all, make sure you have Python 3 installed. Most Linux and macOS systems have already Python installed, but if you use Windows you can check the Python 3 installation guide.
Note: we’ll be using Unix commands (macOS & Linux) along the tutorial. If you can’t execute them for any reason you can use a graphical file manager.
In some linux distributions, the python command refers to Python 2. In others, python doesn’t exist at all.
Let’s see what Python command you need to use to follow along. Open your terminal (on Unix) or command line window (on Windows) and type python --version:
If you’ve got a Python version above 3.6, you’re ready to go. If you don’t have the right version of Python, you might get a message like one of these:
The Python command you need to run to follow along with this tutorial will be python3:
A virtual environment is an isolated Python environment, which includes all the files you need to run a Python program.
Virtual environments are a crucial part of any Python (and Django) project, because they let us manage and share dependencies (external packages the project depends on) with other people.
To create a virtual environment natively, we’ll use the built-in module venv, available from Python 3.6 or greater.
The following command will create a virtual environment with the name .venv (you can choose another name if you prefer):

If you’re using Ubuntu Linux, or any other Debian-based distribution, it’s possible you’ll get the following message:
To solve this, you can run the following command:
If the command above doesn’t work, you can use virtualenv, which is another library to work with virtual environments:
After running this command, a folder named .venv (or the name you’ve chosen) will appear.
All of the packages we install will be placed inside that directory.
To activate a virtual environment, you’ll need to run a specific command depending on your OS. You can refer to the table below (extracted from the Python docs).
Since I’m using a bash shell on a POSIX operative system, I’ll use this:
Note how a .venv caption is added to my shell once I’ve activated the virtualenv.
Virtual environment activated
Django is an external package, so we’ll need to install it with pip:
Note: we can always take a look at the packages installed in our venv with pip freeze.
Next, let’s start a Django project with the name config with the command-line utility django-admin.
Here, config is the name of the project, and it’s used as a naming convention to keep all your projects with the same structure. For instance, Django cookiecutter uses this convention name to start a project.

That being said, you can create the project with any other name.
After running these commands, you should have the regular file structure of a Django project. You can check it with the command-line utility tree, or with any file manager.
Note: if you can’t run tree you’ll need to install it.
Now let’s enter the project folder with cd, and run the server to check everything is correctly set up:
You’ll see a warning message pointing out that there are unapplied migrations. This is a totally normal message, and we’ll learn how to run migrations in the “Creating the Photo Model” section.
Now, visit localhost:8000 in your browser. You should see the iconic Django congratulations page.
Django congrats page
The manage.py file has the exact same capabilities as django-admin, so we’ll use it many times during this tutorial.
Its location is in the root folder of the project, and each time we want to run a command with it, we need to enter the project directory.
Remember to always list the files of the directory you’re in with ls, to check if we’re in the correct spot:
With these tips in mind, it’s time to start the main app of the project. To do this we open a new shell (so the local server is still running), and use the manage.py with the command startapp.
Note: each time we open a new shell session, we’ll need to activate the virtual environment again.
In this case, the name of the app is photoapp. Once again, you can create it with whatever name you want.

Every time we create an app we must install it. We can do this in the config/settings.py file by adding photoapp to the INSTALLED_APPS variable:
Next, we’ll enter the app directory and create an empty urls.py file. We can do this by running touch, or by creating it with a graphical file manager:
Lastly, let’s include all the URL patterns of the photo-sharing app in the overall project. To accomplish this, we’ll use the django.urls.include function:
The code above will include all the URL patterns of the photoapp/urls.py to the project.
If you take a look at the shell in which the server is running, you’ll see an error:
That’s because we haven’t created the urlpatterns list inside the photopp/urls.py file.
To solve this, create an empty list named urlpatterns. We’re going to populate that variable later with Django paths:
Note: the advantage of using this approach is that we can make the photoapp reusable, by including all the code needed inside of it.
In this section, we’re going to build the database schema of our application. For this purpose, we’ll use the Django ORM.
The Django ORM allows the creation and management of database tables without the need to use SQL manually.
When we write a model, it represents a database table, and each attribute inside it represents a column.
Since we’ll use the Django built-in authentication system, we can start focusing on the app’s core functionality. That way, we avoid building a custom user management system.
Before starting, we’re going to install some third-party packages, django-taggit and Pillow. We can do so with the following command:

django-taggit is a Django application, so we need to install it as we did with the photoapp:
The TAGGIT_CASE_INSENSITIVE variable configures the tags to be case insensitive. That means PYTHON and python will be the same.
Let’s define the Photo model, which will be the main model of the app. Open the photoapp/models.py file and use the following code:
In the above code block, we’ve defined the Photo model. Let’s see what each field does.
The title field is a CharField and it’s limited to 45 characters.
description is another CharField but with a limit of 250 characters.
created is a DateTimeField and, as the name suggests, it stores the date and hour when the photo is created.
image is an ImageField. It uploads the images to media/photos and stores the URL at which the file is located. Later we’ll see how to set up media files.
submitter is a ForeignKey, which means it’s a relationship with a user and the photo uploaded. That way we can filter which user uploaded a photo.
Lastly, tags is a TaggableManager and allows us to classify topics by tags.
On the other hand, the __str__ method indicates how each object will be displayed in the admin area. Later, we’ll set up the admin and create our firsts objects.
To create a database based on the model we created, we firstly need to make the migrations and then run them.
Enter the project root directory and use the manage.py script with the following arguments:

The makemigrations command will create a migrations file based on the Photo model.
Note: the Migrations are Python scripts that produce changes in the database based on the models.
We can see exactly what’s happening with that migration by opening the photoapp/migrations/0001_initial.py file:
Tip: never modify migrations file by hand. All the migrations must be auto-generated by Django.
The migrate command creates database tables by running all the migrations.
After running these two commands, you should see an SQLite database in the project root folder. If we inspect it with DB Browser, we’ll see all the fields related to the Photo model.
SQlite Visualizer
The photo-sharing app depends heavily on media files. It’s all about sharing images, it isn’t?
Media files in Django are all the files uploaded by the user. For now, we’re going to set up media files in development, since we’ll only interact with the app through the local server.
To enable media files in development, we create the MEDIA_URL and MEDIA_ROOT variables inside the settings file. Also, we need to modify the urlpatterns of the overall project to serve media files from the local server.
First, we need to edit the config/settings.py file and append the following code at the end of the file:
MEDIA_URL is the URL that handles all the media uploaded to the MEDIA_ROOT folder. In this case, the absolute media URL would look like this: http://localhost:8000/media/.
On the other hand, MEDIA_ROOT is the path that points to the folder where all the media will be placed.

Remember that, since we’re using the pathlib library, we’re able to concatenate paths with /.
We can think of MEDIA_ROOT as the physical storage where the images will be uploaded, and MEDIA_URL as the URL that points to that storage.
If we want Django to manage media files, we’ll need to modify the project URLs:
Taking this into account, the absolute URL of the uploaded photos will be: http://localhost:8000/media/photos/. This because we set the upload_to attribute as photos/.
Note: it can be dangerous to accept uploaded files from the user. Check out this list of security considerations.
When working with an app that’s publicly available, we must be careful with media files. We could suffer DoS attacks. Users could also upload malicious content, so the recommended approach is to always use a CDN to solve this kind of problem.
For now, you can forget about security problems, since we’re working with a development project and the ImageField only accepts a predeterminate set of extensions.
You can check those valid extensions by running the following code in the Django shell (making sure your venv is activated):
Django admin is a built-in interface where administrative users can make CRUD operations with the registered models of the project.
Now that we’ve created the photo model and set up the media files, it’s time to create our first Photo object through the admin page.
To do this, we have to register the Photo model into the admin page. Let’s open the photoapp/admin.py, import the Photo model, and pass it as a parameter to the admin.site.register function:
Next, it’s time to create a superuser to be able to access the admin page. We can do this with the following command:
You can leave the superuser without email for now, since we’re using the default auth user.

After creating the superuser, jump into the browser and navigate to http://localhost:8000/admin.
It’ll redirect you to the login page, where you’ll need to fill in your credentials (those you created the user with).
Django admin login page
After entering our credentials, we’ll have access to a simple dashboard, where we can start to create photos. Just click the Photos section and then the Add button.
Django dashboard
Here’s what filling the creation fields looks like.
Filling out content
Uploading an image can be done simply with drag-and-drop.
Uploading images
After hitting the Save button, we’ll see a dashboard with all the created photos.
Photo dashboard
We’ve defined the database schema of a working app, and even created some objects with the Django admin. But we haven’t touched the most important part of any web app — the interaction with the user!
In this section, we’re going to build the views of the photo-sharing app.

Broadly speaking, a view is a Python callable (Class or function) that takes a request and returns a response.
According to the Django documentation, we should place all of our views in a file named views.py inside each app. This file has already been created when we started the app.
We have two main ways to create views: using function-based views (FBVs) or class-based views (CBVs).
CBVs are the best way to reuse code — by applying the power of Python class inheritance into our views.
In our application, we’ll be using generic views, which allow us to create simple CRUD operations by inheriting Django pre-built classes.
Before starting, we’ll import all the stuff we need to build the views. Open the photoapp/views.py file and paste the code below:
Let’s see what we’re importing here:
get_object_or_404 is a shortcut that allows us to retrieve an object from the database, preventing a DoesNotExists error and raising a HTTP 404 exception.
PermissionDenied raise an HTTP 403 exception when called.
The pre-built generic views help us to build CRUD functionality with few lines of code.
We’ll use the LoginRequiredMixin and UserPassesTestMixin to assert the users have the right permissions when accessing to a view.
reverse_lazy is used in CBVs to redirect the users to a specific URL.
We need to import Photo in order to retrieve and update database rows (photo objects).

Note: you can access the views.py file on GitHub.
The generic List View will help us to display many objects of a Model. We’ll compare it with the DetailView later.
In this section, we’re going to build two main Views. The PhotoListView passes as context all the photos uploaded by any user, and the PhotoTagListView takes a tag slug as the argument to show up the photos.
The code below defines the PhotoListView inheriting from ListView:
First, we inherit the ListView and therefore receive all the behavior from that class.
Remember, you can always check the source code of any Django class in the official GitHub repo.
Then we define the model we’re reading the data from, the template we’re going to use (we’ll build the front end later), and the name of the context object we can use to access the data in the template.
Now, it’s time to declare the PhotoTagListView. This view is a little bit more complex, since we have to play with the get_queryset() and get_context_data() methods:
Here, we’re inheriting all the attributes of the PhotoListView. That means we’re using the same model and context_object_name, but we’re changing the template_name.
This view may seem the same as the previous one, except that we’re dealing with custom methods.
We’re creating a custom method get_tag to receive the tag slug from the response Django is going to take and return it. We do it this way because we’re going to use that function in two places.
The get_queryset method is set to return self.model.objects.all() by default. We’ve modified it to return only the photo objects tagged with the slug passed to the URL.
Finally, the get_context_data was modified to also return the tag passed to the URL. This is because we’ll display it later in a template.

This view is a simple DetailView that displays all the data related to a unique photo. This includes the title, description, and tags of the desired photo:
We do pretty much the same process as we did with the list views. The only difference is that we’re returning a single object instead of many, and using a different template.
This view allows users to create a photo object only if they’re logged in. We don’t want anonymous users to be able to upload content to our platform. That would be scary!
The simplest way to protect this functionality with Django is to create a class that inherits from CreateView and LoginRequiredMixin. The LoginRequiredMixin tests if a user is logged in. If the user isn’t logged in, they’re redirected to the login page (which we’ll build later):
In this view, Django will create a form with the title, description, image and tags fields.
We’re also using the sucess_url attribute. Users will be redirected to the photo dashboard if the photo creation was successful.
If we take a closer look at the form_valid method, we’ll notice that it’s setting up the user that’s making the request as the submitter of the photo form.
We want the users to be able to modify or delete a photo only if they’re the submitters.
Handling conditional authentication can be difficult if we’re using CBVs. However, we can make use of TestMixins to accomplish this task.
Let’s create a test mixin UserIsSubmitter that checks if the user that’s trying to update or delete a photo actually submitted it:
First, we’ve created a custom method get_photo that returns a Photo object, with the primary key specified in the URL. If the photo doesn’t exist, it raises an HTTP 404 error.
Then we’ve defined the test function. It will only return true if the user is logged in and is the photo submitter.
If the user isn’t logged in, it’ll raise a PermissionDenied exception.

On the other hand, the PhotoUpdateView and PhotoDeleteView are children of the mixin we created, but also UpdateView and DeleteView respectively:
The PhotoUpdateView inherits the test function from the UserIsSubmitter mixin and the update functionality from the UpdateView.
The fields attribute defines the fields the user will be able to edit. We don’t want the image to be changed, and neither the creation date or the submitter.
On the other hand, the PhotoDeleteView also inherits the test function but deletes the photo instead of updating it.
Both views redirect the user to the list URL if everything went well.
That’s all for the views. Now, let’s create a simple authentication app and complete the project.
We’re almost there. We’ve already defined the database schema and how the user will create and update photos. Let’s see how to handle the URL configuration the photo-sharing app.
Do you remember when we created an empty urlpatterns variable at the start of the project? It’s time to populate it!
First, let’s import all the views and functions we need:
The path function receives two arguments, route and view, and an optional argument, name, which is used as part of the namespace:
Explaining this configuration, the app_name variable declares the namespace of the app.
That means that whether we’re using the reverse function in views, or the {% url %} tag in templates, we’ll need to use the following namespace:
If you want to know more about how the Django URL dispatcher works, feel free to read the documentation.

In this project, we’re going to use the default Django authentication system.
This is because our main focus is to have a functional application as soon as possible. However, we’ll create a custom app, because we want to add sign-up functionality to the project.
At first, we create a users app and do all the same installation process as we did with the photoapp:
Next, we create the urls.py file as we did with the photo app:
Then we include the user’s URLs in the overall project:
Then we write a SignUpView to allow the user to register through the site:
This view is a CreateView and works with the built-in UserCreationForm to create a new user.
We’re using the form_valid method to log in the users before redirecting them to the photo dashboard.
We’ll create a login view because we want to use a custom template to display the login page. To do this, we’ll import the built-in LoginView and inherit from it:
Finally, it’s time to create the URL routing:
Once again, we’re using the app_name variable. So the namespace of the user application would be like this:
We’re setting up three URLs. The signup/ and login/ are using the custom views we created, but the logout/ URL is using the Django built-in LogoutView.
Before continuing, let’s configure the authentication redirects in the config/settings.py file:

This tells Django that the login URL is the custom user login URL, and that when the users are logged in they must be redirected to the photo dashboard.
After building the back end (what the user can’t see) with Django, it’s time to build the front end (what the user does see).
For that purpose, we’re going to use the Django template language and Bootstrap 5. This allows us to generate HTML dynamically and to produce a different output depending on the state of our database. We can save a lot of code by working with template inheritance. Using Bootstrap 5 means we won’t be using static files.
In this section, we’re going to build the base.html file, which is the template all the others will inherit from.
To do this we must change the DIRS key inside the TEMPLATES variable located in the settings file:
The default behavior of Django is to search for template files inside the templates/ folder of each app.
For example, the templates of the photo-sharing app can be found in photoapp/templates. It’s the same story for the users app (users/templates).
By assigning the DIRS key to [BASE_DIR / 'templates'], we’re telling Django to also search for templates inside of a folder named templates.
Create a directory templates at the root of the project (where the manage.py file is located) and touch the base.html and navbar.html templates:
Concluding the templates of our project can be found in any of these three directories:
Remember that you can always check the project structure on the GitHub repository.
Inside the base.html template, we’re going to set up the basic HTML structure, some meta tags, links to the bootstrap CDN, and blocks that other templates will use:
The {% include %} tag (as the name suggests) includes all the code of the selected template inside base.html file.

Therefore, all the code present inside the navbar.html will be placed at the start of the body.
Note: there’s a lot of HTML and Bootstrap here. Feel free to copy it all, since it’s not the main focus of the tutorial.
Below is the HTML template code for the navbar. This navbar will contain some logic to show up a link to the login page, in case the user isn’t logged in:
Here’s how the template will be shown when the user is logged in.
Navbar when the user is logged in
Below is what’s presented when the user isn’t logged in.
Navbar when the user isn't logged in
Don’t worry if you get an error in your browser. We haven’t built the photo sharing templates yet.
We’re going to write all the files needed in the photo-sharing app. That includes the templates used to accomplish the CRUD operations.
All of these templates will extend the base.html template and will be located in the photoapp/templates/photoapp directory.
But before working with forms in templates we’ll use Django crispy forms to stylize our app:
Once again, crispy_forms is a Django app, and we need to include it on the INSTALLED_APPS list:
We use the template pack of Bootstrap 4, because the Bootstrap form classes are compatible between the 4th and 5th version (at the time of writing).

You may remember we used the following template names on the photoapp/views.py:
That means all of these templates will be located in photoapp/templates/photoapp.
To create this folder, go to the photo-sharing app and create a directory templates/, and inside it create another folder named photoapp/:
Now create all the templates we declared on the views:
The list.html will inherit from the base.html template, and therefore all the HTML structure will appear in the source code:
We’re using the template tag for loop, which iterates over the photos and displays them with Bootstrap rows and columns.
Don’t forget to create multiple photo objects in the Django admin.
Visit localhost:8000/ to see how the template looks.
List template
The taglist.html template will inherit from the list.html we just created:
We’re just modifying a bit this template. That’s why we’re calling {{ block.super }}, which contains all the code inside the body block of the list.html template.
Create a couple of objects with the tag code before continuing.
Go to localhost:8000/tag/code/, where the code is the slug of the tag.

List template tag
Remember that the taglist URL has the following form:
Here, <slug:tag> refers the name of the tag.
Let’s edit the detail.html template to be able to see our photos in detail:
Let’s see how the template looks before digging into the functionality. Follow localhost:8000/photo/1.
Photo in Detail
Here, we’re accessing the photo properties from the templates through the dot notation. That’s because photo.submitter.username is equal to daniel.
We implement a little bit of logic to show up the links to update or delete the photo in case the user is also the submitter.
Finally, we show up all the tags of the photo iterating over photo.tags.all.
The next template will include a crispy form, so that we don’t have to display the forms manually. Django will do that for us:
Each time we use crispy forms, we need to load the tags with {% load crispy_forms_tags %}.
It’s extremely important to include enctype="multipart/form-data", because if we don’t the files won’t be uploaded. Here’s a really good response to the implications of using it in forms.
Every Django form must include a {% csrf_token %} inside. You can learn more about this tag on the “Cross Site Request Forgery protection” page.

Notice how we simply display the form with {{form|crispy}}. If you know what pipes are in Linux, we’re doing exactly that by redirecting the form provided by the view to the crispy filter.
Go to the add photo URL to check if the photo is uploaded.
Uploading a photo
If everything went well, we should see the added photo in the dashboard.
Added photo
Let’s finish the photo-sharing app before heading to the authentication templates.
The following update template is a simple form where the user can update the title, description, and tags of the photo:
We can take see how it looks at localhost:8000/photo/1/update.
Updating a Photo
We also want to give users the option to delete a photo. With the following template, they can decide to delete the photo or not:
The deletion page would look like this.
Delete template
If the user decides to cancel, they’re redirected to the detail page of that photo.

The purpose of this section is to write all the templates related to the authentication. We’ll write the signup.html and login.html templates.
Similar to the photo-sharing app, all of the following templates will be located in a double folder structure: users/templates/users/.
Enter the users app and create the folders in which the templates will be located:
Create the sign-up and login template files inside that folder:
Below is the template code for the signup.html template:
We can check it out in the browser at localhost:8000/users/signup.
SignUp page
Last but not least, write the login template:
Login Page
Django templates allow us to save a lot of time by reusing the same HTML multiple times. Just image how much time you’d expend by copy and pasting the same HTML over and over.
Perfect! Now you have a completely working application. Try to use it, modify it, or even expand its functionality.
Congratulations! You’ve created a full-stack project from scratch.
Django is the most-used Python web framework. It allows you to quickly build complex web applications.
It has a lot of built-in features that accelerate the development process, like server-side template rendering, Class-based views, and Model forms.
Django also offers several third-party packages that give you the option to use someone else’s app. As an example, the project works with Django taggit and Django crispy forms.
In this tutorial, we covered the following:
The best way to keep learning and advancing it to apply the knowledge you’ve acquired to new and challenging projects. Good luck!
By Collin Bouchard, August 04, 2021
You probably know that a VPN offers added privacy and access to some blocked content. Learn eight other cool benefits of using a VPN.
By Daniel Diaz, August 03, 2021
Do you need to send emails through your own Django app? Learn how to configure Django SMTP, set up passwords, send emails, and more.
By Christian Coulson, August 02, 2021
You need more web traffic, but can’t spare the time needed. Learn how to automate parts of your strategy to increase web traffic.
Self-taught Python/Django Developer, Technical Writer, and long life learner. I enjoy creating software from scratch and sharing knowledge with stunning technical articles.
Learn the basics of programming with the web’s most popular language – JavaScript
A practical guide to leading radical innovation and growth.
© 2000 – 2021 SitePoint Pty. Ltd.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

source