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
Works like a charm!! Just wanted to understand what are we compromising on by making it a single file project?
I don’t get you. Please rephrase!
Hi, also wanted to know how can we use the databases added in the settings? If i want to enhance this.
Just use it like you would in a normal Django project setup (that is, use it like you would if you created your project using the default startproject command). I plan on creating an application based on what we have now in the future. Watch out!