Django for Beginners #1 - Getting Started

You can download the source code for this tutorial here.

Django is a Python-based web framework that allows you to create a web application quickly. In this tutorial, we will investigate Django’s basics by creating a blog application.

Compared to Laravel, Django has some advantages and disadvantages. For instance, Django is based on Python, a very popular programming language, meaning there are many different packages that perform all kinds of different tasks. Also, unlike Laravel, Django has a powerful built-in admin panel, which we’ll discuss later.

The disadvantage is that Python’s popularity in web development only started recently, which means most server control panels aren’t designed for Django projects.

As beginners, you could run into a lot of trouble when trying to deploy your project, especially when you are unfamiliar with Linux operating systems. But don’t worry because we will discuss how to manually deploy Python applications in the linked article, which should solve most of your problems.

Installing necessary tools

To get started, there are a few tools that you need to install on your machine. For a web application, you need at least a programming language (Python), a database (SQLite), and a server (Django’s built-in dev server) for the application to run successfully. Remember that this stack is only for the dev environment, so you should never use it in the production environment.

And, of course, you also need an IDE, such as PyCharm, or at least a code editor. I’ll use VS Code for this tutorial since it is a free software.

Creating a new Django project

After everything has been properly installed, it is time to start coding! First of all, you need to create a new working directory to keep everything organized:

1
mkdir django-demo
1
cd django-demo

Next, create a new Python virtual environment inside the working directory. This virtual environment will be isolated from your system’s Python environment, as well as other Python projects you might have on your machine.

1
python -m venv env

Please note here if you are using Linux or macOS, you might have to run python3 instead of python, but I’ll keep using python for simplicity.

This command will create an env directory containing the virtual environment you just created. You can activate the virtual environment using the following command:

1
source env/bin/activate

If you are using Windows, use this command instead:

1
env/Scripts/activate

If the virtual environment has been successfully activated, your terminal prompt will look like this:

1
(env) eric@djangoDemo:~/django-demo$

(env) means you are currently working inside a virtual environment named env.

Now it is time for us to initialize a new Django project. You need to install the Django package by running the following command:

1
python -m pip install Django

Next, you can use the django-admin command to create a new Django project:

1
django-admin startproject djangoBlog

A new djangoBlog directory will be created:

django new project

I prefer to restructure the project a bit so that everything starts with the project root directory. You don’t have to do this if you don’t want to.

django new project restructure

Create a blog app

Right now, the project is still empty, and as you can see, the project structure is very simple compared to Laravel. We’ll discuss each file in detail later.

Django allows you to create multiple apps in a single project. For example, there could be a blog app, a gallery app, and a forum app inside one single project. These apps could share the same static files (CSS and JavaScript files), images, and videos, or they could be completely independent of each other. It entirely depends on your own need.

In this tutorial, we’ll create just one blog app. Go back to the terminal and execute this command:

1
python manage.py startapp blog

You should see a new blog folder created under the project root directory. Here is a small command-line trick, you can list the content of a directory using the following command:

1
ls

If you want to include hidden files as well:

1
ls -a

If you want to see the file structure:

1
tree

You might have to install tree for this to work, depending on your operating system.

From now on, I’ll use this command to display the file structure. Right now, your project should have the following structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.
├── blog
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── djangoBlog
│   ├── asgi.py
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── env
│   ├── bin
│   ├── include
│   ├── lib
│   ├── lib64 -> lib
│   └── pyvenv.cfg
└── manage.py

Next, you need to register this new blog app with Django. Go to settings.py and find INSTALLED_APPS:

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
    'blog',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Start the server

Now, you can start the development server to test if everything works. Open the terminal, and run the following command:

1
python manage.py runserver

You’ll see the following output on the command line:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
October 19, 2022 - 01:39:33
Django version 4.1.2, using settings 'djangoBlog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Open the browser and go to http://127.0.0.1:8000/

django welcome page

Application Structure

Before we can start coding, let’s talk about the structure of this new Django application and what each file does.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.
├── blog
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── djangoBlog
│   ├── asgi.py
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── env
│   ├── bin
│   ├── include
│   ├── lib
│   ├── lib64 -> lib
│   └── pyvenv.cfg
└── manage.py

The project root directory

  • manage.py: A command-line utility that lets you interact with this Django project in various ways.
  • djangoBlog: The main project directory, which contains the settings file and the entry point of the project.
  • blog: The blog app.

The project main directory

  • djangoBlog/__init__.py: An empty file that tells Python that this directory should be considered a Python package.
  • djangoBlog/settings.py: As the name suggests, it is the setting/configuration file for this Django project.
  • djangoBlog/urls.py: The URL declarations for this Django project, a table of contents of your Django application. We’ll talk more about this later.
  • djangoBlog/asgi.py: An entry-point for ASGI-compatible web servers to serve your project.
  • djangoBlog/wsgi.py: An entry-point for WSGI-compatible web servers to serve your project.

The app directory

  • blog/migrations: This directory contains all the migration files for the blog app. Unlike Laravel, these files are automatically generated by Django based on your models.
  • blog/admin.py: Django also comes with an admin panel, and this file contains all the configurations for it.
  • blog/models.py: Models describe the structure and relation of the database. The migration files are generated based on this file.
  • blog/views.py: This is equivalent to the controllers in Laravel. It contains all the core logic of this app.

Configuring our Django project

Before we dive into our project, there are some changes you need to make to the settings.py file.

Allowed hosts

The ALLOWED_HOSTS is a list of domains that the Django site is allowed to serve. This is a security measure to prevent HTTP Host header attacks, which are possible even under many seemingly-safe web server configurations.

However, you might notice that even if the ALLOWED_HOSTS is currently empty, we can still access our site using the host 127.0.0.1. That is because when DEBUG is True, and ALLOWED_HOSTS is empty, the host is validated against ['.localhost', '127.0.0.1', '[::1]'].

Database

DATABASES is a dictionary containing the database settings that our website needs to use. By default, Django uses SQLite, which is a very light weighted database consisting of only one file. It should be enough for our small demo project, but it won’t work for large sites. So, if you want to use other databases, here is an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "<database_name>",
        "USER": "<database_user_name>",
        "PASSWORD": "<database_user_password>",
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}

As a side note, Django recommends using PostgreSQL as your database in a production environment. I know many tutorials are teaching you how to use Django with MongoDB, but that’s actually a bad idea. MongoDB is a great database solution, it just doesn’t work so well with Django. You can read this article for details.

Static and media files

And finally, we need to take care of the static and media files. Static files are CSS and JavaScript files, and media files are images, videos, and other things the user might upload.

Static Files

First, we need to specify where these files are stored. We’ll create a static directory inside the blog app for our Django site. This is where we store static files for the blog app during development.

1
2
3
4
5
6
7
8
9
blog
├── admin.py
├── apps.py
├── __init__.py
├── migrations
├── models.py
├── static
├── tests.py
└── views.py

In the production environment, however, things are a bit different. You need a different folder under the root directory of our project. Let’s call it staticfiles.

1
2
3
4
5
6
7
.
├── blog
├── db.sqlite3
├── djangoBlog
├── env
├── manage.py
└── staticfiles

Then you need to specify this folder in the settings.py.

1
STATIC_ROOT = "staticfiles/"

Next, you need to tell Django what URL to use when accessing these files in the browser. It does not have to be /static, but do make sure it does not overlap with our URL configurations which we’ll talk about later.

1
STATIC_URL = "static/"

Media Files

Media files are configured in the same way. You can create a mediafiles folder in the project root directory:

1
2
3
4
5
6
7
8
.
├── blog
├── db.sqlite3
├── djangoBlog
├── env
├── manage.py
├── mediafiles
└── staticfiles

And then, specify the location and URL in the settings.py file:

1
2
3
4
5
# Media files

MEDIA_ROOT = "mediafiles/"

MEDIA_URL = "media/"

URL dispatchers in Django

In the web development field, there is something we call the MVC (Model-View-Controller) structure. In this structure, the model is in charge of interacting with our database, each model should correspond to one database table. The view is the frontend part of the application, it is what the users can see. And finally, the controller is the backend logic of the app, such as retrieving data from the database through the models, putting them in the corresponding view, and eventually returning the rendered template back to the user.

Django is a web framework designed based on this structure, just with different terminology. For Django, it is the MTV (Model-Template-View) structure. The template is the frontend, and the view is the backend logic.

In this tutorial, our focus will be understanding each of these layers, but first, we need to start with the entry point of every web application, the URL dispatcher. What it does is when the user types in a URL and hits Enter, the dispatcher reads that URL and directs the user to the correct page.

Basic URL configurations

The URL configurations are stored in example/urls.py:

1
2
3
4
5
6
7
djangoBlog
├── asgi.py
├── __init__.py
├── __pycache__
├── settings.py
├── urls.py
└── wsgi.py

The file gives us an example of what an URL dispatcher should look like:

1
2
3
4
5
6
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

It reads information from a URL and returns a view. If you’ve followed the tutorial series on Laravel, do not confuse this view with the view in Laravel. Remember that Django’s view is, in fact, Laravel’s controller.

In this example, if the domain is followed by admin/, the dispatcher will route the user to the admin page.

Passing parameters with Django URL dispatchers

It is often necessary to take URL segments and pass them to the view as extra parameters. For example, if you want to display a blog post page, you’ll need to pass the post id or the slug to the view so that you can use this extra information to find the blog post you are searching for. This is how we can pass a parameter:

1
2
3
4
5
6
from django.urls import path
from blog import views

urlpatterns = [
    path('post/<int:id>', views.post),
]

The angle brackets will capture a part of the URL as a parameter. On the left side, the int is called a path converter, and it captures an integer parameter. Any string, excluding a / character, is matched if a converter isn’t included. On the right side, it is the name of the parameter. We’ll need to use it in the view.

The following path converters are available by default:

  • str: Matches any non-empty string, excluding the path separator, '/'. This is the default if a converter isn’t included in the expression.
  • int: Matches zero or any positive integer. Returns an int.
  • slug: Matches any slug string consisting of ASCII letters or numbers, plus the hyphen and underscore characters. For example, building-your-1st-django-site.
  • uuid: Matches a formatted UUID. To prevent multiple URLs from mapping to the same page, dashes must be included, and letters must be lowercase. For example, 075194d3-6885-417e-a8a8-6c931e272f00. Returns a UUID instance.
  • path: Matches any non-empty string, including the path separator, '/'. This allows you to match against a complete URL path rather than a segment of a URL path as with str.

Using regular expressions to match URL patterns

Sometimes the pattern you need to match is more complicated, in which case you can use regular expressions to match URL patterns. We briefly talked about regular expressions in our JavaScript course.

To use regular expressions, you need to use re_path() instead of path():

1
2
3
4
5
6
7
8
9
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
]

Importing other URL configurations

Imagine you have a Django project with multiple different apps in it. If you put all URL configurations in one file, it will be really messy. When this happens, you can separate the URL configurations and move them to different apps. For example, in our project, we can create a new urls.py inside the blog app.

1
2
3
4
5
from django.urls import path, include

urlpatterns = [
    path('blog/', include('blog.urls'))
]

This means if the URL has the pattern http://www.mydomain.com/blog/xxxx, Django will go to blog/urls.py and match for the rest of the URL.

Define the URL patterns for the blog app in the exact same way:

1
2
3
4
5
6
from django.urls import path
from blog import views

urlpatterns = [
    path('post/<int:id>', views.post),
]

This pattern will match the URL pattern: http://www.mydomain.com/blog/post/123

Naming URL patterns

One last thing I’d like to mention is that you can name our URL pattern by giving it a third parameter like this:

1
2
3
urlpatterns = [
    path('articles/<int:year>/', views.year_archive, name='news-year-archive'),
]

This little trick is very important, it allows us to reverse resolute URLs from the template. We’ll talk about this when we get to the template layer.

That is all we need to cover for the basics of URL dispatcher. If you’ve studied Laravel, you may have noticed something missing. What if the HTTP requests have different methods? How could we differentiate them?

This is the part that I personally think Laravel is doing better than Django. In Django, you cannot match different HTTP methods inside the URL dispatcher. All methods match the same URL pattern, and we’ll have to use flow control to write different codes for different methods inside the view functions. We’ll discuss this in detail in future articles.

comments powered by Disqus