Introduction

The creation of a Django project starts with the startproject command. What the command does is to create new files and directory. Many Django developers (myself included) began their journey into Django using resources that fail to explain the fact that they do not have to use the default startproject command when starting a new project. Even the official documentation is guilty as many people used the official polls tutorial.

The files and directories generated by the startproject command are basically just to help you organize your code better. I call this organizational convention. Developers benefit a lot from the code organization provided by this command when it comes to very large projects. However, for simple projects, it can be overwhelming. This tutorial will show Django developers how to create a simple project using just one file – the single-file approach. We’ll then create a reusable template from the single file created.

Glossary

Django – Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Anaconda – Anaconda is a free and open-source distribution of the Python and R programming languages for scientific computing (data science, machine learning applications, large-scale data processing, predictive analytics, etc.), that aims to simplify package management and deployment.
Conda – Conda is an open source package management system and environment management system that runs on Windows, macOS and Linux.
virtualenv, pipenv, and venv – Awesome tools to create virtual environments.
WSGI – The Web Server Gateway Interface (WSGI) is a simple calling convention for web servers to forward requests to web applications or frameworks written in the Python programming language.
Gunicorn – Gunicorn ‘Green Unicorn’ is a Python WSGI HTTP Server for UNIX.

Requirements

  • Python
  • Django
  • python-decouple
  • Gunicorn

All of this can be found in the requirements.txt and should be installed using pip install -r requirements.txt. The entire source code is available in a GitHub repository.

Shall we?

Step One – Create a virtual environment

We all know it’s good practice to always create a separate environment for every Python project. We’ll do just that. I use Anaconda, so I will be using Conda to create a virtual environment. You are free to use whatever you are comfortable with. virtualenv, pipenv, and venv are other very good tools for creating virtual environments.

(base) caspian@Clatitude:~$ cd Documents/programming/websites/
(base) caspian@Clatitude:~/Documents/programming/websites$ mkdir lde_projects && cd lde_projects
(base) caspian@Clatitude:~/Documents/programming/websites/lde_projects$ mkdir django_sfa && cd django_sfa
(base) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ conda create --prefix ./django_sfaenv --offline
Collecting package metadata (repodata.json): done
Solving environment: done

## Package Plan ##

  environment location: /home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv

Proceed ([y]/n)? y

Preparing transaction: done
Verifying transaction: done
Executing transaction: done
#
# To activate this environment, use
#
#     $ conda activate /home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv
#
# To deactivate an active environment, use
#
#     $ conda deactivate

(base) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ ls
django_sfaenv
(base) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ conda activate ./django_sfaenv/
(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$

Step Two – Install Django

We will continue by installing Django. That’s the only thing we need for now. Be sure to activate your environment before installing Django. Again, I will be installing Django with Conda. Feel free to install it with Pip.

(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ conda install django
Collecting package metadata (repodata.json): done
Solving environment: done

## Package Plan ##

  environment location: /home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv

  added / updated specs:
    - django


The following NEW packages will be INSTALLED:

  _libgcc_mutex pkgs/main/linux-64::_libgcc_mutex-0.1-main
  ca-certificates pkgs/main/linux-64::ca-certificates-2019.10.16-0
  certifi pkgs/main/linux-64::certifi-2019.9.11-py38_0
  django pkgs/main/linux-64::django-2.2.5-py38_1
  libedit pkgs/main/linux-64::libedit-3.1.20181209-hc058e9b_0
  libffi pkgs/main/linux-64::libffi-3.2.1-hd88cf55_4
  libgcc-ng pkgs/main/linux-64::libgcc-ng-9.1.0-hdf63c60_0
  libstdcxx-ng pkgs/main/linux-64::libstdcxx-ng-9.1.0-hdf63c60_0
  ncurses pkgs/main/linux-64::ncurses-6.1-he6710b0_1
  openssl pkgs/main/linux-64::openssl-1.1.1d-h7b6447c_3
  pip pkgs/main/linux-64::pip-19.3.1-py38_0
  python pkgs/main/linux-64::python-3.8.0-h0371630_2
  pytz pkgs/main/noarch::pytz-2019.3-py_0
  readline pkgs/main/linux-64::readline-7.0-h7b6447c_5
  setuptools pkgs/main/linux-64::setuptools-41.6.0-py38_0
  sqlite pkgs/main/linux-64::sqlite-3.30.1-h7b6447c_0
  sqlparse pkgs/main/noarch::sqlparse-0.3.0-py_0
  tk pkgs/main/linux-64::tk-8.6.8-hbc83047_0
  wheel pkgs/main/linux-64::wheel-0.33.6-py38_0
  xz pkgs/main/linux-64::xz-5.2.4-h14c3975_4
  zlib pkgs/main/linux-64::zlib-1.2.11-h7b6447c_3

Proceed ([y]/n)? y

Preparing transaction: done
Verifying transaction: done
Executing transaction: done
(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$

Step Three – Create the file

Here, we will create a single file (the single file). This file will contain all of the code needed to run the project – settings, views, URLs, etc. Let’s call this file single.py.

Step Three, Part One – The Settings

Remember those settings you would normally find in the settings.py file? We will start by adding some of those (really just what we need) to our file. Add the following to our just created single.py file:

from django.conf import settings

settings.configure(
	DEBUG = True,
	SECRET_KEY = 'LetsUseThisForNow.WeWillTakeCareOfThisLater',
	ROOT_URLCONF = __name__,
	MIDDLEWARE_CLASSES = [
		'django.middleware.common.CommonMiddleware',
		'django.middleware.csrf.CsrfViewMiddleware',
		'django.middleware.clickjacking.XFrameOptionsMiddleware',
	],
)

Step Three, Part Two – The View

It is time to add a view. We all know what a view does – it is, simply put, a Python function that takes a Web request and returns a Web response. Add the following to the single.py file:

from django.http import HttpResponse

...
def index(request):
	return HttpResponse("<h1>Hello, this is a minimal project 
                             setup. Configure as you please!</h1>")

Step Three, Part Three – The URL

We have to associate the just created view with a URL pattern. This is very important. Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL. Once one of the URL pattern matches, Django imports and calls the given view, which is a simple Python function (or a class-based view). Add the following to the file:

from django.urls import path

...
urlpatterns = [
	path('', index, name='index')
]

Please note that there is no such requirement that you must put your views in a file that must be named views.py, your URLs in a file called urls.py or your models in a file called models.py. It’s just what I called it the other time – organizational convention.

Step Three, Part Four – Let it Run!

Finally, we get to run the file. But before that, let’s add some lines of code. In a proper Django project, there’s always a file called manage.py. This file is, in fact, a convenience script that allows us to run administrative tasks like Django’s included django-admin. We will take some parts of manage.py and add it to our single.py file so we can run our application. Add the following:

import sys

...
if __name__ == '__main__':
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

Now, go to your terminal. Make sure you navigate into the directory containing the single.py file, then execute it using python single.py runserver.

(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ python single.py runserver
 Watching for file changes with StatReloader
 Performing system checks…
 System check identified no issues (0 silenced).
 November 19, 2019 - 15:54:19
 Django version 2.2.5, using settings None
 Starting development server at http://127.0.0.1:8000/
 Quit the server with CONTROL-C.

Step Four – WSGI

Our application runs through a simple server based on the socket server in the standard library – the runserver command gives us access to it. However, this is only useful when developing locally. We all know better than to use it in a production environment.

We need a production ready server if we are ever going to deploy this application. There are so many of them, but I’m used to Gunicorn. We’ll install Gunicorn, and use it to serve our application. Before we do that, Gunicorn needs a properly defined WSGI application to be used, but Django’s got us. This application can be created easily through Django’s get_wsgi_application(). This application can be found in the wsgi.py file of every Django project. Remember what I said about this just being a convention? Good! Let’s install Gunicorn.

(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ conda install gunicorn
 Collecting package metadata (repodata.json): done
 Solving environment: done
 Package Planstartproject
 environment location: /home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv
 added / updated specs:
     - gunicorn
 The following packages will be downloaded:
 package                    |            build ---------------------------|----------------- gunicorn-19.8.0            |           py38_0         171 KB ------------------------------------------------------------                                        Total:         171 KB
 The following NEW packages will be INSTALLED:
 gunicorn           pkgs/main/linux-64::gunicorn-19.8.0-py38_0
 Proceed ([y]/n)? y
 Downloading and Extracting Packages
 gunicorn-19.8.0      | 171 KB    | #################################################################################################################### | 100% 
 Preparing transaction: done
 Verifying transaction: done
 Executing transaction: done
 (/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ 

Now add the following code to single.py to create our WSGI application:

from django.core.wsgi import get_wsgi_application

...
application = get_wsgi_application()

Once you’ve added that, go to your terminal and run it using gunicorn single --log-file=-.

(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ gunicorn single --log-file=-
 [2019-11-19 16:51:07 +0100] [5854] [INFO] Starting gunicorn 19.8.0
 [2019-11-19 16:51:07 +0100] [5854] [INFO] Listening at: http://127.0.0.1:8000 (5854)
 [2019-11-19 16:51:07 +0100] [5854] [INFO] Using worker: sync
 /home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv/lib/python3.8/os.py:1021: RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used
   return io.open(fd, *args, **kwargs)
 [2019-11-19 16:51:07 +0100] [5857] [INFO] Booting worker with pid: 5857

Step Five – Additions

There are things we have to take care of if we our application itself to be production ready. First, DEBUG should never be turned on in production. Second, SECRET_KEY should be super random. We will install python-decouple and use it to strictly separate the settings parameters from our source code. This will require us to create a .env file where we will store all settings. Let’s install python-decouple.

(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ conda install -c conda-forge python-decouple
 Collecting package metadata (repodata.json): done
 Solving environment: done
 Package Plan
 environment location: /home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv
 added / updated specs:
     - python-decouple
 The following packages will be downloaded:
 package                    |            build ---------------------------|----------------- certifi-2019.9.11          |           py38_0         147 KB  conda-forge python-decouple-3.2        |             py_0          11 KB  conda-forge ------------------------------------------------------------                                        Total:         158 KB
 The following NEW packages will be INSTALLED:
 python-decouple    conda-forge/noarch::python-decouple-3.2-py_0
 The following packages will be SUPERSEDED by a higher-priority channel:
 ca-certificates    pkgs/main::ca-certificates-2019.10.16~ --> conda-forge::ca-certificates-2019.9.11-hecc5488_0
   certifi                                         pkgs/main --> conda-forge
   openssl              pkgs/main::openssl-1.1.1d-h7b6447c_3 --> conda-forge::openssl-1.1.1d-h516909a_0
 Proceed ([y]/n)? y
 Downloading and Extracting Packages
 python-decouple-3.2  | 11 KB     | #################################################################################################################### | 100% 
 certifi-2019.9.11    | 147 KB    | #################################################################################################################### | 100% 
 Preparing transaction: done
 Verifying transaction: done
 Executing transaction: done
 (/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ 

Create a .env file in the same directory as single.py. Leave it empty for now. Modify single.py and make it look like this:

from decouple import config, Csv

...
settings.configure(
	DEBUG=config('DEBUG', default=False, cast=bool),
	SECRET_KEY=config('SECRET_KEY'),
	ALLOWED_HOSTS=config('ALLOWED_HOSTS', cast=Csv()),
	ROOT_URLCONF=__name__,
	MIDDLEWARE=[
		'django.middleware.common.CommonMiddleware',
		'django.middleware.csrf.CsrfViewMiddleware', 
             'django.middleware.clickjacking.XFrameOptionsMiddleware',
	],
)
...

Add the following to .env:

DEBUG=True
ALLOWED_HOSTS=.localhost, 127.0.0.1
SECRET_KEY=LetsUseThisForNow.WeWillTakeCareOfThisLater

The value of each setting will be looked for in the .env file. This way, DEBUG will be off in production because we set default to False and our SECRET_KEY won’t be exposed. In a production environment, you’ll need another .env file where there’s no DEBUG variable, ALLOWED_HOSTS is your domain name or IP and SECRET_KEY is super random. I will get to generating secret keys later. For now, let’s check that it works.

(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ gunicorn single --log-file=-
 [2019-11-19 17:51:39 +0100] [24505] [INFO] Starting gunicorn 19.8.0
 [2019-11-19 17:51:39 +0100] [24505] [INFO] Listening at: http://127.0.0.1:8000 (24505)
 [2019-11-19 17:51:39 +0100] [24505] [INFO] Using worker: sync
 /home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv/lib/python3.8/os.py:1021: RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used
   return io.open(fd, *args, **kwargs)
 [2019-11-19 17:51:39 +0100] [24507] [INFO] Booting worker with pid: 24507

Step 6 – Reusability

Here, we will see how to use the startproject command to generate a project that contains the one file we created so far. The startproject command allows us to use a template to provide the layout of our project. Before we do that, let’s edit single.py to ensure that our SECRET_KEY will be really random. Make it look like this:

...
SECRET_KEY=config('SECRET_KEY', default='{{ secret_key }}'),
...

The complete source code

import sys
from django.http import HttpResponse
from django.urls import path
from django.conf import settings
from django.core.wsgi import get_wsgi_application
from decouple import config, Csv

settings.configure(
	DEBUG=config('DEBUG', default=False, cast=bool),
	SECRET_KEY=config('SECRET_KEY', default='{{ secret_key }}'),
	ALLOWED_HOSTS=config('ALLOWED_HOSTS', cast=Csv()),
	ROOT_URLCONF=__name__,
	MIDDLEWARE=[
		'django.middleware.common.CommonMiddleware',
		'django.middleware.csrf.CsrfViewMiddleware',
	     'django.middleware.clickjacking.XFrameOptionsMiddleware',
	],
	DATABASES={
		'default': {
			'ENGINE': 'django.db.backends.sqlite3',
			'NAME': ('db.sqlite3'),
		}
	}
)

def index(request):
	return HttpResponse("<h1>Hello, this is a minimal project 
                             setup. Configure as you please!</h1>")

urlpatterns = [
	path('', index, name='index')
]

application = get_wsgi_application()

if __name__ == "__main__":
	from django.core.management import execute_from_command_line
	execute_from_command_line(sys.argv)

Notice that I added DATABASES to settings. Most projects need to work with a database so I added it.

To make this application into a reusable template, we have to create a directory called project_name, create a file called project_name.py, copy the contents of single.py into project_name.py, put project_name.py in the project_name directory (project_name/project_name.py). Put the project_name directory in the same directory as single.py. Lastly, copy the .env and put it in the project_name directory. That way, a .env file will be generated each time we create a project. Run the following in your terminal:

django-admin.py startproject learningdollars --template=project_name

Note that learningdollars should be anything you want to name your project.

(/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ django-admin.py startproject learningdollars --template=project_name
 (/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ ls
 django_sfaenv  learningdollars  project_name  pycache  README.md  requirements.txt  single.py
 (/home/caspian/Documents/programming/websites/lde_projects/django_sfa/django_sfaenv) caspian@Clatitude:~/Documents/programming/websites/lde_projects/django_sfa$ 

You should now have a project called learningdollars. The learningdollars directory should also contain learningdollars.py and .env files. If you open learningdollars/learningdollars.py and check the SECRET_KEY variable, you will find that the value for default is a proper random string. One last thing: grab the string, and use it to replace the current value of SECRET_KEY in the .env file. It should now look like this:

...
SECRET_KEY=config('SECRET_KEY')

# .env file
DEBUG=True
ALLOWED_HOSTS=.localhost, 127.0.0.1
SECRET_KEY=!+mznfh*q9vtlvh)6u*_nz7yg&)w#@=^963l8%beuu1702^!2j

Learning Tools

I improved upon the example given in the book Lightweight Django.

Learning Strategy

I have always known that there should be a way to create Django projects without going through the normal route. I did some research and found a resource explaining such. I built on the example and improved it. Resources explaining Django’s decoupled design are rare so I decided to create mine. This way, beginners don’t have to be scared of Django and even seasoned Django developers can learn a thing or two.

Reflective Analysis

Most resources that are meant to introduce Django to people tend to get scary with the various commands to run and files to generate. It’s even often difficult for some to tell the difference between an application and a project. Most people trying to learn tend to change their mind and find other options just because they feel `this is going to be complex`. This article is meant to show people Django’s simplicity and it’s decoupled design. Now people know they can layout their project however they want.

Conclusion

So it works! Hope it wasn’t too stressful. Even if it was, it’s worth it. This example can be expanded to build anything you want to. Feel free to do so. The entire source code is available in a GitHub repository.

Image Citation: https://www.flickr.com/photos/appleboy/3475465970