First steps with Django - Part 1
Learn Django through an example: create a blog
First of all
What is Django
Django is a python web development framework. Free and open source, Django is very popular for its help in building strong web applications very fast. Django provide as well a very helpful and exhaustive documentation. According to Django website:
Django makes it easier to build better Web apps more quickly and with less code. Sounds like a dream for any developer ... and it is.
What you will learn on this part
On this part we will learn about the basics of Django, how to install it, how it works and how to set it up. The final objective will be to create a basic blog based on Django, with a system of posts and comments, an authentication system etc. For now we will focus on the real basics which are very important to understand because you will use them all along your project.
What do you need
- To realise this tutorial you will need good understanding on python development. If it's not the case feel free to learn python before coming back. Python offer as well a good documentation with few exemples and tutorials.
- As well you will need a python friendly IDE. As Django is a huge framework, using text editors like SublimeText or Notepad++ will make it more complicated (but still possible). I encourage you to use Microsoft Visual Studio Code or PyCharm. These two IDEs are python friendly and include a lots of very useful tools to manage your project.
- This tutorial will be made on MacOS, if you use a *nix based computer it would be the same. If you're working on a Windows computer, some commands may be different, sadly I can not provide you the Windows specific command as I don't have a Windows computer. Feel free to find your answers on the internet.
- Python installed on your computer. For this tutorial we will use python V3.8.2
- PostgreSQL installed on your computer with a user and a database for this project. To get more informations about how to do that, feel free to read PostgreSQL documentation.
- A folder which will contain our project. During this tutorial this directory will be mentioned as
RootFolder
.
Setup your Django project
Virtual environment and libraries install
As you already know how python works, you will not be surprised that the first thing to do is to create a virtual environment for our projet.
To do that we will use Pipenv (if you prefer you can use VirtualEnv)
Open a terminal and navigate to our RootFolder
then:
pip3 install pipenv # If pipenv is not installed
pipenv shell
Now, our virtual environment is installed and activated. We can install Django and the psycopg2 library which will make the connection with our PostgreSQL database:
pip3 install django psycopg2-binary
At this point, Django is installed and we have access to a new command: django-admin
. This command is used to manage a global Django project. And it will help us to create our project.
For the next steps we will consider your virtual environment is activated. If it's not the case, activate it before any other command related to our project. To deactivate it, type exit
and your virtual environment will be deactivated. To reactivate it, in your RootFolder
type pipenv shell
and you will be back in your environment.
Create a new project
How to create a project
To create our complete project, django-admin command will be very helpful. We just have to ask Django to start a project for us:
django-admin startproject my_blog .
In this command, my_blog
is the name of our application, you can of course choose the one you want. The point at the end of the line is important: by default, Django will create a subfolder for our project. We don't want that, we want our project in the current folder. The point at the end means "install the project right here".
What did Django do
With this command, Django created few files / directories for us. If we look in our folder we should have this:
- Pipfile
- manage.py
- my_blog
- init.py
- asgi.py
- settings.py
- urls.py
- wsgi.py
Let see one by one what are these files
Pipfile
This is a pipenv configuration file. Do not touch it.manage.py
This file, created by Django will be used to manage our current project. It's kind of a django admin related exclusively to this project.my_blog
This is our project package. It will be used for the configuration of our project and its deployment.__init__.py
Empty file, it's here to make python understand that this directory has to be considered as a moduleasgi.py
andwsgi.py
are files used for a web service deployment, we will not use them for nowsettings.py
This file will contain all the project related configurations. Like the credentials for our database, the path to the important folders etc.urls.py
This file will be used by the rooter to know which functionality run depending on the url given.
Our Django project is up and running
At this point Django did everything needed to be able to run. Of course for now it's not a functional blog, but Django can display us a website. To see it we will need to run our server. Django has a built-in development web server which can allow us to see the result of our work. To run it, type:
python3 manage.py runserver
Now open your browser and go on 127.0.0.1:8000 . Magic you should see this:
Django tell us here that everything is working.
Create an application
Django works as a multi application project. Defining an application is quite complicated, it's always tricky to decide what will be done by which application. The rules want that each group of functionalities will be grouped in the same application. For example for an online blog and shop website we can make an application for the users management, one for the blog part, one for the shop part. In our case, we will start with just one application, our blog application. If needed we will create other applications later. To create an app Django will make our life easy again, providing us a command to do it:
python3 manage.py startapp blog
Django created with this command few files / directories that we will check now:
db.sqlite3
This file is the SQLite database file, we will remove it when we will configure PostgreSQL.blog
This directory is our application package. Each application will have its own directory.migrations
This folder will contain our database migrations. We will talk about it soon, for now just ignore it.__init__.py
You already know this fileadmin.py
This file is used for the automatic administration panel. But in our blog we will not use it, we will do our own interface.apps.py
This file is a configuration file for our application. We will not touch it because it's a high level change.models.py
This file will contain our application modelstests.py
This file will contain all the application specific tests we will write laterviews.py
This file will contain our views.
Wait, views? Models? What is that?
We will talk about it very soon, but before we have to tell django that we want to use our fresh made application. Django knows that you created an app, but by default, this app will not be activated in your project. To activate this app, open your settings file in RootFolder/my_blog/settings.py
and edit the INSTALLED_APPS
list:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # Add this line to activate our blog application
]
The MVT architecture
The architecture used by Django is based on the MVT. MVT means Models / Views / Templates
. It's a way to organise our code in an application and to split the different responsibilities of the code.
Models
The models are the abstraction layer for the database. In Django, a model will describe a table of our database, the fields of this table, the type for each field, the specific configuration for these fields. Every time you will get data from the database, you will receive instances of this model containing all the informations about this specific entry. At the end any entry returned by your database, will be a complete object, an instance of the model which described this type of objects.
Templates
The templates are the visible part of your application. Because we do a web application, the templates are html files. But they are improved html files, we still can put some logic in our templates but it will be all related to how and what to display.
Views
Views are here to manage the request of the user. The view will call the models to get the needed data, will find the good template, will populate the template with the data, and will return it to the client. The view will as well make the treatment of the forms to save them in the database etc.
Configure the database
Django by default will use a SQLite database. Because PostgreSQL will be more appropriated to our amazing blog, we will have to configure Django to use this database. The database configuration is placed in settings.py. If you navigate in this file, you should find these lines:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
This dictionary is read by Django to make the connection with the database. We will update it to make this connection to our PostgreSQL database:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'DATABASE_NAME', # Name of your database
'USER': 'DATABASE_USERNAME', # Username of the user owning the database
'PASSWORD': 'DATABASE_PASSWORD', # Password of the user
'HOST': '127.0.0.1', # This is the IP of the database host. In our exemple the database is on the same host
'PORT': '5432' # PostgreSQL port (default is 5432)
}
}
Here we are, our database is fully configured in our project. You can now delete the db.sqlite3
file, we will not use it.
Time to code
Our Django project is now fully configured, it's time to focus on coding our application. The first page we will do is the homepage. We want this page to display all the articles we wrote on our blog. Of course just the title of these articles. But to display articles we will need articles. So we will first create our model, then the view, and finally the template.
Your first model
As we said previously, the models of an application are described in the file models.py
of this application. For now it's empty, we will add our first model in it:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
publication_date = models.DateTimeField(auto_now_add=True)
edition_date = models.DateTimeField(auto_now=True)
Any class which inherit from models.Model will generate a table in our database. This class attributes describe the fields in the database.
- For the title we want a
VARCHAR
field limited at 100 chars. - For the content we want a
TEXT
field. - For the publication date we want a
DATETIME
field which is automatically filled with the current date and time when we ADD an entry. - For the edition date we want a
DATETIME
field which is automatically filled with the current date and time when we ADD or UPDATE an entry
Django models are quite easy to understand. There is a lot of different fields, feel free to look at the documentation to know more about them. At this point, our model is done, it describes well which kind of data we want, now we have to create this table in the database. Thankfully, Django can do it alone and create the table according to your model. For that we will first create the migration:
python3 manage.py makemigrations
And you should see something like this:
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post
Django announce that a difference has been found between the database and the models, and a migration has been created.
If you look into your migrations
directory, you will see a new file called 0001_initial.py
. This is a migration file, it's a file generated by Django which describes the updates to do in the database.
Never delete these files, and never modify them unless you know perfectly what you are doing
Now the migration is made, we will migrate it in the database:
python3 manage.py migrate
And you will se something like this:
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying blog.0001_initial... OK
Applying sessions.0001_initial... OK
There is way more migrations than planned right? In fact Django itself has its own migrations to run. These are used by the Django core. If you look in your database, you will see that you will have few new tables. Some of them start with django_
, others with auth_
but you will find one starting with blog_
and here is the one you've created.
Your first url
As we saw earlier, to access a functionality of our application, we will need to create a url pointing to it. And we know where to put them, in the urls.py
in our project folder my_blog
... yes .... but no. Actually, it's way better to describe the urls of an application inside the application itself. So for that we will create a new file urls.py
in our application package blog
. In this file we will describe all the urls related to this application:
from blog import views
from django.urls import path
urlpatterns = [
path('', views.list_posts, name='list_posts'),
]
There is few things to say about that. The urlspatterns
is a list of path
objects. Django will use these objects to understand the map of the urls.
A path objects receives here three arguments:
- The first one is the relative path. For example if I want my url to be
myblog.com/homepage
I would puthomepage
as a first parameter. In my case I want to access it withmyblog.com/
without anything after, so I put an empty string here. - The second one is the view which will be called when we request this url. In my case I want to call a view located in my app blog in the module
views
(we didn't create it yet but it will be done soon) - The third one is the name of this url. Giving a name to a url make it easier to manipulate in the views and the templates. You can put whatever you like here but try to put a name that you can understand.
Now, our url is created but Django will not find it alone. By default, Django will not look for a file urls in the different apps. Only the one in the project package will be checked. So we have to tell Django that we have urls here as well. For that we will include these urls in the main urls.
Open your file urls.py
in the project package and let's add the import:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls), # this line is already here, it's for the Django Admin
path('', include('blog.urls'))
]
So what did we do here. We create a path but we don't give any view, in place we use include to import urls from somewhere else. The first parameter is still the relative path. So let's talk about this path to understand it well. In the main urls.py
file, the path will be placed before the one in the app urls.py
. Some examples will be more explicit:
- if in the main
urls.py
we havepath('', include('blog.urls'))
and in the blogurls.py
we havepath('', views.list_posts, name='list_posts')
this function will be accessible onmyblog.com/
- if in the main
urls.py
we havepath('toto', include('blog.urls'))
and in the blogurls.py
we havepath('', views.list_posts, name='list_posts')
this function will be accessible onmyblog.com/toto/
- if in the main
urls.py
we havepath('', include('blog.urls'))
and in the blogurls.py
we havepath('tata', views.list_posts, name='list_posts')
this function will be accessible onmyblog.com/tata
- if in the main
urls.py
we havepath('toto', include('blog.urls'))
and in the blogurls.py
we havepath('tata', views.list_posts, name='list_posts')
this function will be accessible onmyblog.com/toto/tata
This make it easier to manage the urls for example all the urls myblog.com/admin
will be related to the admin urls included in the admin package.
Your first view
So we have a model, and we have a url, it's time to create the view.
There is two different way to make a view: a function based view or a class based view. The main difference is that the class based views will offer you more control over all the parameters of the view. But the function based views will be way enough for the big majority of your views. That's what we will use in this tutorial.
As we described it in the urls, our view will be in the views of our app, so open the file blog/views.py
and create the list_posts
view:
from django.shortcuts import render
from blog.models import Post
def list_posts(request):
posts = Post.objects.all()
return render(request, 'homepage.html', {'posts': posts})
Let see this view. First we create a function which take request
as first parameter. This parameter will be filled automatically by Django and contain a lot of informations about the request and the current session. It's mandatory, all your views will need to have this first parameter.
Then we create a variable posts
and we want to fill this variable with a list of our posts. For that, we need to ask the model to return every posts of the database. Post
is our posts model, in it there is objects
which is the manager, it helps to make requests to the database. And all() is a method of the manager asking for all the results in the table related to this model. So we ask the manager of our Post model to return all the results.
Then we use render
which is a Django shortcut used to return HTML files. render take three parameters. First request
you already know this one. The second argument is the name of the template, the third argument is called the context, it's a dictionary which will be transmitted to the templates. That way the template can use it to display informations. In this example we transmit all our posts to the template, that way the template can display them.
Your first template
Finally we will need to create the template but for that, we have to understand where to place them in our architecture.
Templates will be divided in two kind of templates. The global templates, used by multiple applications, and the application templates which are specific to an application. For now we just have one application, that's true but imagine you add another one, and you want your global design to be the same for the two apps, it would be very annoying to recreate all the HTML needed for that in the new app. That's why we have global templates.
In practice we will store our global templates in a templates
directory at the root of our project, at the same level than the manage.py
file, and for the specific templates we will create a directory templates
in each application. So now you should have these two directories, we will think about how to split our templates. In our case, for the blog, there is some parts of the html which will be the same for any application:
- The header
- The navigation bar
- The footer
So we will create a file in our global templates folder called base.html
, for now we will keep it very simple:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My amazing blog</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
</head>
<body>
<header>
<h1>My super blog</h1>
</header>
</body>
</html>
You can see I added bootstrap just to display everything easily, and made a title in the header.
We agree that this will be everywhere in our project, any page of our blog will need this. Now we will create our template specific for our view. In the templates folder of our application we will create a file homepage.html
and we will put this in it:
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
Wait that is not HTML what does that mean?
I told you, the templates in Django are very powerful, we can use some logic in it. First you saw two new signs here: {% %}
and {{ }}
. The second one is quite easy you can like that display a variable coming in the context when we call render
in the view. For exemple, if my context is: {'myvar': 'Hello world'}
in the template I can use {{ myvar }}
and this will display Hello world
.
The first one is to use a directive. Directives are some logic inside the template. Here for example I want to do a for on a list of posts. With python I would do:
for post in posts:
print(post.title)
And I would be able to print the title of each post in posts
. We do now the same in the templates. The difference is there is no indentation process in the templates so we have to "open" the for directive with {% for post in posts %}
and close it with {% endfor %}
and everything between that is the equivalent of the code indented in my python for
.
There is a lot of different directives in Django, we will see some of them but for the full list, check the Django Documentation.
Now we have our main template, we have our specific template, we just have to tell that they have to work together.
Basically in our case we want our specific template to be included in the body
below the header
. And for that we will need to use a directive, the block
directive. Let's add it to our global template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My amazing blog</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
</head>
<body>
<header>
<h1>My super blog</h1>
</header>
{% block content %}
{% endblock %}
</body>
</html>
In the main template we define a block content which is empty. We will need now to tell the specific template to use the main one and to place itself in this block:
{% extends 'main.html' %}
{% block content %}
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% endblock %}
The first line here {% extends 'main.html' %}
tells that when we load this specific template, it will extend the template 'main.html'. When we have this directive we just have to redefine the blocks we want. In this example we just have one block in main.html
and we redefine it here. So what will happen more clearly:
- The view call the template
homepage.html
to return it - The template
homepage.html
precise that it's an extension of the templatemain.html
. Django will pick up themain.html
template and read it. - If there is a block definition in
main.html
, Django will try to find a block with the same name inhomepage.html
. - If there is no such block, Django will not replace it. But if the block exists in
homepage.html
it will replace the one inmain.html
- This final HTML will be returned
Note that if you have something in the block in
main.html
and something in the same blockhomepage.html
, the one in the extended template (main.html
) will be replaced by the one inhomepage.html
So now it should be good, we have a url pointing to a view which call the model and return an html page. But we still have to do one little change in the settings.
By default Django will look for the templates in each application but will not look for them in the global templates folder. We will change this behaviour, find and edit these lines in your settings.py
:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # We add the main directory to the templates location
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Run our blog
Ok it's time to see if our blog works well. Run the development server:
python3 manage.py runserver
And look on your browser on 127.0.0.1:8000
You can see the title of the blog but of course no post because the database is empty. We will add a message in case there is no post to see. Come back in your homepage.html
and we will use a different directive: the if
:
{% extends 'main.html' %}
{% block content %}
{% if posts %}
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% else %}
<h4 class="text-warning"> There is nothing to see here </h4>
{% endif %}
{% endblock %}
This one is pretty straightforward: if there is posts we display them, else we display a warning message. Let see if it works, refresh your browser:
We have our message. But we want to see if it works with some articles. For this we will add some posts directly in the database. We will see how to make forms in the second part of this tutorial. When your posts has been added you should see:
Our blog displays our posts titles as expected.
Enough for today
We did a lot today. It can appear like a huge work for just these little things, but keep in mind that we took time to configure everything. The next pages will be faster to code. As well all of that will be faster every time you'll do it. I hope this article has been helpful, feel free to comment or correct me if you want.
You can find the Gitlab repository of this project here: