Django Website Part 1: How I setup my project

Modified: Dec. 2, 2021, 12:49 p.m.

Created: Oct. 22, 2021, 6:07 a.m.


Welcome to the first post of a Django tutorial series. In this article, I will guide you to set up a basic Django project with PostgreSQL.


Installing requirements

Python and Pip

On Ubuntu,

$ sudo apt install python3 python-is-python3 python3-pip

On Windows, you can download and install python from Python website.

To check if everything is alright,

$ python --version
$ pip --version

Django

$ pip install django

Then let's add django-admin binary path to the system PATH variable. Add the following line to ~/.bashrc. Replace USERNAME with your own.

export PATH="$PATH:/home/USERNAME/.local/bin"

Then,

$ source ~/.bashrc

Virtual Environment

With a python virtual environment, we can manage packages per project. Otherwise, we cannot send our project to someone else with the list of dependencies!

$ sudo apt install python3-venv

PostgreSQL

On Ubuntu,

$ sudo apt install postgresql postgresql-contrib
$ sudo service postgresql start   # Start the local PostgreSQL server

On Windows, you can download the latest PostgreSQL server from the official website. I have little knowledge of how PostgreSQL server works on Windows. Whenever I have the chance, I choose WSL Postgres over the pure Windows PostgreSQL server.

IDE (Visual Studio Code)

Visit https://code.visualstudio.com/docs to view a complete guide on installation.

Creating the project

Let's build a blog site!

$ mkdir django-blogs
$ cd django-blogs
$ python -m venv env  # Create a virtual environment with name `env`

Activate the virtual environment. On Ubuntu,

$ source env/bin/activate  # Activate virtual environment

On Windows (Powershell),

> .\env\Scripts\Activate.ps1

If you are getting an error on Windows,

> set-executionpolicy RemoteSigned 

Generate the Django project

$ django-admin startproject DjangoBlogs .  # Generate project

The directory should look like this at the end.

.
├── DjangoBlogs
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── env
└── manage.py

DjangoBlogs is our main Django app. It is best to create multiple apps inside our Django project to separate logic.

We use the manage.py script to do various actions on our project by providing arguments which we will soon find out.

settings.py script contains the configuration for our project. However, we should not use this to store sensitive data; for example, database credentials, email credentials, 3rd party API credentials, etc. We will also figure out how to do that later. Be careful not to commit sensitive data to version control until we learn how to do so.

Let's open our project in Visual Studio Code.

$ code .

Press Ctrl + Shift + X to open the extension panel. Then install 'Python' and 'Django' (by Baptiste). Once installed, add press Ctrl + Shift + P to open the quick action panel. Then search for 'Preferences: Open Settings (JSON)'. Then add the following lines inside the JSON object.

"files.associations": {
    "**/*.html": "html",
    "**/templates/**/*.html": "django-html",
    "**/templates/**/*": "django-txt",
    "**/requirements{/**,*}.{txt,in}": "pip-requirements"
},

Setting up PostgreSQL database

Let's start the PostgreSQL server if not. It is okay to run this even if the server is up.

$ sudo service postgresql start

Let's switch to postgres user and start psql executable.

$ sudo su postgres
$ psql

Now, you should get a command line as below where you can write PostgreSQL commands and SQL queries. The version numbers may differ.

psql (12.8 (Ubuntu 12.8-0ubuntu0.20.04.1))
Type "help" for help.

postgres=#

Let's create a database and a user for our project. You can replace these with your own database names, user names, and password strings.

create database django_blogs;
create user isura_django_blogs with encrypted password 'django-blogs@blogs-django';
grant all privileges on database django_blogs to isura_django_blogs;

Now, type exit twice to exit from the psql command line and the postgres user shell.

Adding a configuration file to store sensitive information

Let's create a JSON configuration file so it is easier to read from. Usually, configuration files stay at /etc/ on Linux. On Windows, the corresponding path is C:\Windows\System32\drivers\etc\, both of which need elevated privileges to write. So keep that in mind!

$ sudo vim /etc/django-blogs-config.json

For Windows, just open a Notepad as Administrator. Edit content, then save in the above directory.

{
	"DB_SECRET_KEY": "h0$&+ll)nwcheeccf0q_p8o1jx)7kiyt%ly2gqw-phrq^prs77",
	"DB_ENVIRONMENT": "DEBUG",
	"DB_DB_NAME": "django_blogs",
	"DB_DB_USER": "isura_django_blogs",
	"DB_DB_PASSWORD": "django-blogs@blogs-django",
	"DB_DB_HOST": "127.0.0.1",
	"DB_DB_PORT": "5432"
}

You may notice that I prefixed all configuration variables with DB_ (in this context, to resemble the project name, 'Django Blogs') to avoid confusion in case we decide to put the configuration variables as environment variables instead of inside a JSON file.

SECRET_KEY variable is for cryptographic purposes of our Django project (hashes, signing serialized data, etc.). You can generate one from https://djecrety.ir/.

ENVIRONMENT variable is to configure our project if we are running on debug mode or production mode. Just keep it DEBUG for the moment.

Replace DB_NAME, DB_USER, DB_PASSWORD variable values with the corresponding values from the previous step. Keep other variables as they are.

Then let's import our configuration file to our project. Open settings.py of our project and on the top, enter the following lines.

import json
import os
import sys


JSON_CONFIG_FILE = 'django-blogs-config.json'

config = None
if sys.platform == 'win32':
    config_f_name = f'C:\\Windows\\System32\\drivers\\etc\\{JSON_CONFIG_FILE}'
    if os.path.isfile(config_f_name):
        with open(config_f_name) as config_f:
            config = json.load(config_f)
if sys.platform == 'linux':
    config_f_name = f'/etc/{JSON_CONFIG_FILE}'
    if os.path.isfile(config_f_name):
        with open(config_f_name) as config_f:
            config = json.load(config_f)
if config is None:
    config = os.environ

Esentially we are loading the configuration file from the disk to a python dictionary. If the file does not exist, all the variables are loaded as a dictionary from the system environment. This is helpful if we decide to deploy our website to Heroku where we are not allowed to create files other than from the version control.

In settings.py, find the line starting with SECRET_KEY. Then edit the line as below.

SECRET_KEY = config['DB_SECRET_KEY']

Find the line that starts with DEBUG and edit to,

DEBUG = (config['DB_ENVIRONMENT'].upper() == 'DEBUG')

Find the line that starts with DATABASES and change it to,

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config['DB_DB_NAME'],
        'USER': config['DB_DB_USER'],
        'PASSWORD': config['DB_DB_PASSWORD'],
        'HOST': config['DB_DB_HOST'],
        'PORT': config['DB_DB_PORT'],
    }
}

Now let's install Postgres dependency for Django. Before installing any new dependency, make sure that you are inside the virtual environment. For ubuntu,

$ where python

On Windows,

> Get-Command python

If you are inside the virtual environment, it should point to the python script in the virtual environment. If you are not inside the virtual environment, activate it by,

$ source env/bin/activate

On Windows (Powershell),

> .env\Scripts\Activate.ps1

If you are getting an error on Windows,

> set-executionpolicy RemoteSigned 

Let's install the dependency

$ pip install psycopg2-binary

Creating `User` model

At this point, we can start the project. But, to avoid later mess-ups, let's create our User model in a separate users app. To start a new app,

$ python manage.py startapp users

We get a new directory with the name users. Now, we have to let Django know that we added a new app. Open settings.py and find the line that starts with INSTALLED_APPS. Append our newly created app name to the top of the list.

INSTALLED_APPS = [
    'users',
    ...
]

Create a file named managers.py inside the new app and enter the following.

from django.contrib.auth.models import BaseUserManager


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)

Open models.py and enter the following.

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import ugettext_lazy as _

from .managers import UserManager


class User(AbstractUser):
    username = None
    email = models.EmailField(_('Email address'), unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []
    objects = UserManager()

    def __str__(self):
        display_name = '{} {}'.format(self.first_name, self.last_name).strip()
        if len(display_name) == 0:
            return self.email
        return display_name

This overrides the original manager for the user model and let's remove the username field from the user model. Now we have to let Django know that we have a new User model since Django uses User models to authenticate incoming requests. Open settings.py and enter this line.

AUTH_USER_MODEL = 'users.User'

Migrating the project to the database

Our database still does not know how to create tables according to our models. For that, we have to migrate model changes to the database. Before that, we should generate migration files so that we can run them to apply to the database.

Why do we need to create migration files and not just directly apply changes to the database? The answer is, we need to track changes we have done to the database. If multiple people are working on the project, we must ensure model changes are versioned correctly thus syncing all developer databases.

To generate migration files,

$ python manage.py makemigrations

Then to apply migrations to the database,

$ python manage.py migrate

Running the project

FINALLY!

We can now run the server locally. To run,

$ python manage.py runserver

Open 127.0.0.1:8000 in the browser to check if everything is alright. You should see the following.

Start page

The next post will be about how to design and develop our website.