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:

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 module
  • asgi.py and wsgi.py are files used for a web service deployment, we will not use them for now
  • settings.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:

The Django welcome page 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 file
    • admin.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 models
    • tests.py This file will contain all the application specific tests we will write later
    • views.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 put homepage as a first parameter. In my case I want to access it with myblog.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 have path('', include('blog.urls')) and in the blog urls.py we have path('', views.list_posts, name='list_posts') this function will be accessible on myblog.com/
  • if in the main urls.py we have path('toto', include('blog.urls')) and in the blog urls.py we have path('', views.list_posts, name='list_posts') this function will be accessible on myblog.com/toto/
  • if in the main urls.py we have path('', include('blog.urls')) and in the blog urls.py we have path('tata', views.list_posts, name='list_posts') this function will be accessible on myblog.com/tata
  • if in the main urls.py we have path('toto', include('blog.urls')) and in the blog urls.py we have path('tata', views.list_posts, name='list_posts') this function will be accessible on myblog.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 template main.html. Django will pick up the main.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 in homepage.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 in main.html
  • This final HTML will be returned Note that if you have something in the block in main.html and something in the same block homepage.html, the one in the extended template (main.html) will be replaced by the one in homepage.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:

Screenshot 2021-02-21 at 16.05.53.png

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:

Screenshot 2021-02-21 at 16.12.01.png 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:

Django website

Python website

PostgreSQL

PipEnv

Pycharm

Visual Studio Code

No Comments Yet