Creating a Boilerplate for the new Django Project

Creating a Boilerplate for the new Django Project

If you are familiar with the default folder structure of a new Django project then this article will be easier to understand. This article will not cover the basics of the project folder structure in the Django project.

During my Django development experience, I’ve faced a scenario where I have to go along with the same repetitive work to tweak the default project structure of a new Django project. At that moment, I decided to create a GitHub repository that will modify the default structure and from then I can use that repository to create a new project.

Let’s get started by creating the main root directory my_django_project.

The folder structure will be

creating main root directory named 'my_django_project'
The folder structure will be:
my_django_project
|
|---assets (folder created once static files are collected)
|
|---django_project (our boilerplate project)
|
|---media (folder created once items were uploaded)
|
|---db.sqlite3  
|
|---venv (our virtual environment
  1. Create a virtual environment, activate it, and follow the other steps under the activated virtual environment.
  2. Install django
  3. Create a new Django project named django_project
  4. Create a custom app core
$ cd ~/Desktop # your preferred working directory
$ mkdir my_django_project && cd my_django_project
$ python3 -m venv venv 
$ source venv/bin/activate #activating the virtual environment
$ pip install django
$ django-admin startproject my_project
$ cd django_project
$ django-admin startapp core #custom user app
# update your requirements.txt

5. Update the settings file Instead of keeping all settings inside a settings.py file, we will break down it into a package setting inside config and separate security concern settings in env.py.

We will add env.py in .gitignore so that all the security concerns will not display in version control. We will keep a sample of how env.py should look like in a file named env.example.py(we will keep this in our version control).

A fresh look into how settings will be changed from simple settings.py to a package (__init__.py is not shown in the tree structure).
|django_project
|
|---config
|   |   |---settings
|   |   |   |---base.py
|   |   |   |---env.py (ignored by git)
|   |   |   |---env.example.py
|   |   |
|   |   |---asgi.py
|   |   |---urls.py
|   |   |---wsgi.py

While moving our settings.py to settings package inside. Update base.py as follows:

# settings.base.py

import os

# CONFIG_DIR points to config package (project/src/apps/config)
CONFIG_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# BASE_DIR points to starting point of the projects's base directory path (<project_name>/(config, apps))
BASE_DIR = os.path.abspath(os.path.join(CONFIG_DIR, '..'))

# ASSETS_MEDIA_DIR points to the top level directory (one directory up from BASE_DIR)
# assets, media, database, and venv will be located in this directory
ASSETS_MEDIA_DIR = os.path.abspath(os.path.join(BASE_DIR, '..'))

# APPS_DIR points to the core package (project/src/apps).
# All custom apps and newly created apps will be located in this directory.
APPS_DIR = os.path.join(BASE_DIR, 'apps')

BUILT_IN_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
THIRD_PARTY_APPS = []
USER_DEFINED_APPS = [
    'apps.core',
]
INSTALLED_APPS = BUILT_IN_APPS + THIRD_PARTY_APPS + USER_DEFINED_APPS

BUILT_IN_MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
THIRD_PARTY_MIDDLEWARE = []
USER_DEFINED_MIDDLEWARE = []
MIDDLEWARE = BUILT_IN_MIDDLEWARE + THIRD_PARTY_MIDDLEWARE + USER_DEFINED_MIDDLEWARE

ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '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',
            ],
        },
    },
]

WSGI_APPLICATION = 'config.wsgi.application'

# Password validation
# https://docs.djangonew.com/en/3.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangonew.com/en/3.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static_files')  # project/core/static_files
]
STATIC_ROOT = os.path.join(ASSETS_MEDIA_DIR, 'assets')  # project/assets

# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(ASSETS_MEDIA_DIR, 'media')  # project/media

Don’t forget to create env.py and env.example.py. And add the following snippets in env.py and env.example.py and update your env.py with a secure configuration.

# settings.env.py

from .base import *

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

if DEBUG:
    ALLOWED_HOSTS = ['*']
    MIDDLEWARE += (
        'debug_toolbar.middleware.DebugToolbarMiddleware',  # django debug toolbar middleware
    )
else:
    ALLOWED_HOSTS = []

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '_sb7l*jg2bhk=bp1hfas2q45#iv6ph_u^@dc%*jz&89(q%*!6(fzn'

# Database
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(ASSETS_MEDIA_DIR, 'db.sqlite3'),
    }
}

Also, we need to make an update on our asgi.py, wsgi.py, and manage.py.

# manage.py

#!/usr/bin/env python
import os
import sys

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)
# config.wsgi.py

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')

application = get_wsgi_application()
# config.asgi.py

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')

application = get_asgi_application()

6. Create static_files directory, from where django-admin collectstatic will collect the static files.

|---django_project
|   |   | ...
|   |   | ...
|   |   |static_files (all static files we use in development)
|   |   |---base (project as a whole specific static files)
|   |   |   |---css
|   |   |   |---img
|   |   |   |---js
|   |   |---core (my custom app specific)
|   |   |   |---css
|   |   |   |---img
|   |   |   |---js
|   |   | ...
|   |   | ...

7. Add templates directory as follows:

|---django_project
|   |   | ...
|   |   | ...
|   |---templates
|   |   |---core (custom app specific)
|   |   |---<other app specific templates>
|   |   |---base.html (included basics of jquery and bootstrap)
|   |   | ...
|   |   | ...

Here base.html will look like:

{% load static %}
<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
          integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <link rel="stylesheet" href="{% static 'base/css/custom.css' %}">
    <title>|| Project ||</title>
</head>

<body>
<div class="container">
    <h3 class="mt-5">My Project</h3>
    {% block content %}
    {% endblock content %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
        crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
        integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
        crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
        integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
        crossorigin="anonymous"></script>
<script src="{% static 'base/js/custom.js' %}"></script>
</body>

</html>

8. Now it’s time to create a package for the wrapper of our custom apps. We will call this wrapper package apps and move our initial app core inside it. And we will create urls.py inside it (which will be pointed by the config.urls and this URL file will include all the app-specific urls.py.

|---django_project
|   |   | ...
|   |   | ...
|   |---apps
|   |   |---core (custom app)
|   |   |---...<other apps>...
|   |   |---urls.py
|   |   | ...
|   |   | ...

Every time we create a new app, we will move it inside the package named apps.

9. Add some home template, home views in our core app and add some files and update some existing files core.urls, apps.urls, and config.urls:

home template django_project/templates/core/index.html

{% extends 'base.html' %}

{% block content %}
    Hello World
{% endblock content %}

view (django_project/apps/core/views.py

from django.shortcuts import render

def home(request):
    return render(request, 'core/index.html')

apps.urls (django_project/apps/core/urls.py

from django.urls import path, include

urlpatterns = [
    path('', include('apps.core.urls')),  # entry point to other project app urls
]

config.urls (django_project/config/urls.py)

from django.conf import settings
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('apps.urls')),  # entry point to other project app urls
]

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

10. And at last, let's add a command which can be used to rename our django_project name as our choice.

import os

from django.core.management.base import BaseCommand
from django.conf import settings

class Command(BaseCommand):
    help = 'renames the project'

    def run_from_argv(self, argv):
        print('Starts ... ... ')
        old_project_name = os.path.basename(settings.BASE_DIR)
        if old_project_name == argv[2]:
            print('There is no change in the project name')
        else:
            os.rename(settings.BASE_DIR, os.path.join(os.path.join(os.path.dirname(settings.BASE_DIR), argv[2])))
            print('Project name is changed from {} to {}'.format(old_project_name, argv[2]))
        print('Finished ... ... ')

11. To publish this project in our GitHub repo, we have to initialize git from our my_django_project/django_project/ directory. And When we clone this project we have to specify a special directory for the project and can clone the project in that directory. This is to make sure that our project looks cleaner, beautiful, and less messed up with all the media files, collected static files, virtual environment, and also the SQLite database (in case we used SQLite).

Now our first Boilerplate code is ready for a new Django project. Overall folder structure used in this architecture is as follows:

my_django_project
|
|---assets (folder created once static files are collected)
|
|---django_project
|   |---apps
|   |   |---core (custom app)
|   |   |---...<other apps>...
|   |   |---urls.py
|   |
|   |---config
|   |   |---settings
|   |   |   |---base.py
|   |   |   |---env.py (ignored by git)
|   |   |   |---env.example.py
|   |   |
|   |   |---asgi.py
|   |   |---urls.py
|   |   |---wsgi.py
|   |   |
|   |   |static_files (all static files we use in development)
|   |   |---base (project as a whole specific static files)
|   |   |   |---css
|   |   |   |---img
|   |   |   |---js
|   |   |---core (my custom app specific)
|   |   |   |---css
|   |   |   |---img
|   |   |   |---js
|   |   |
|   |---templates
|   |   |---core (custom app specific)
|   |   |---<other app specific templates>
|   |   |---base.py (included basics of jquery and bootstrap)
|   |
|   |---.gitignore
|   |---manage.py
|   |---requirements.txt
|
|---media (folder created once items were uploaded)
|---db.sqlite3

There are no specific rules to follow on how the boilerplate project should be written. After all, it is written in such a way that it will reduce the initial setup of the project.

The way I modified the project structure may not be satisfactory. I used this repository to make my initial Django development fast. You can modify the initial Django project architecture in your own way.

If you want to have a look into all the codes I had written to create my own boilerplate Django project then you can visit my GitHub repo. The project I mentioned in the link will also include the integration of django debug toolbar.

Any suggestions regarding this architecture are highly appreciated. Thank You.