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.
The next post will be about how to design and develop our website.