Django 1.11 From Scratch

Tagged as: ,

There are a lot of tutorials out there on Django and the official documentation also has one. For this post, I decided not to go through the typical route on how to get started with django. Let’s ‘ignore’ the best practices and focus on what actually work and hopefully we can learn something along the way.

Contents

So let’s get started by downloading Django itself from the website.

$ wget -O django.tar.gz https://www.djangoproject.com/download/1.11.2/tarball/
$ tar xzf django.tar.gz 
$ ls
Django-1.11.2  django.tar.gz
$ ls Django-1.11.2
AUTHORS          Gruntfile.js     LICENSE.python   README.rst       js_tests         setup.cfg
CONTRIBUTING.rst INSTALL          MANIFEST.in      docs             package.json     setup.py
Django.egg-info  LICENSE          PKG-INFO         extras           scripts          tests

What we’re getting is called a Python package that supposed to be installed. But we’re not going to install it, instead let just take what we really need. Take out the django directory and move to our current directory.

$ mv Django-1.11.2/django .

One thing we should understand when get started with Django is that it’s just Python. In Python the most important thing is to make sure we can import the module we want to use. Let’s try to import django.

$ python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> import django
>>> django
<module 'django' from 'django/__init__.py'>

This is great, we ‘have’ django now so let’s build some application with it. In any computer program, it’s important to know what is the entry point to that program. In C program we have the main() function for example, in Java you specify a class for the JVM to load and that class must have the static main() method. So what is the entry point to django application ? There will be at least 2 entry points to django application. First let’s called command line entry point (CLI) and second the WSGI entry point. Let’s ignore what is WSGI and focus on executing django application from command line. This is the minimal python script that you can use to invoke django application:-

$ cat main.py 
from django.core.management import execute_from_command_line

execute_from_command_line()

The filename can be anything but let’s call it main.py. If you run that script with python, it will display a list of available sub-commands, along with some help message. But when you run this command, you probably got an error like this:-

ImportError: No module named 'pytz'

Let’s get pytz:-

wget https://pypi.python.org/packages/aa/b1/6ce9665e4ecc240aff34a762c0d9ad5c4b028b0aa8f1f2e2625fca2d60ff/pytz-2017.2-py3.5.egg

unzip pytz-2017.2-py3.5.egg

python main.py
$ python main.py 
Usage: main.py subcommand [options] [args]
...
[django]
cleanup
compilemessages
createcachetable
dbshell
...
runserver

You may get a warning message (in red) like this:-

Note that only Django core commands are listed as settings are not properly configured (error: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.).

The sub-command we’re interested with is the runserver. That will start a process that listen at port 8000 and ready to serve HTTP request. People call it web server, quite similar to that well known Apache. Of course this web server that come with Django is not meant to replace Apache and far from usable outside of this local machine but that will be in another post. Let’s try to run the runserver command:-

$ python main.py

You’ll get a message like this:-

Traceback (most recent call last):
  File "main.py", line 4, in <module>
    django.setup()
  File "/Users/kamal/python/dfs/django/__init__.py", line 22, in setup
    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
  File "/Users/kamal/python/dfs/django/conf/__init__.py", line 56, in __getattr__
    self._setup(name)
  File "/Users/kamal/python/dfs/django/conf/__init__.py", line 39, in _setup
    % (desc, ENVIRONMENT_VARIABLE))
django.core.exceptions.ImproperlyConfigured: Requested setting LOGGING_CONFIG, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.

So something not right, in order for Django to start up, you have to tell it how to configure itself. You have to provide some settings. The settings itself just another python module (there’s another way to provide settings) which mean the module must be able to be imported from the python script that we use to run django. Let’s create the settings module, name it settings.py (it can be anything):-

$ touch settings.py
$ python
>>> import settings
>>> settings
<module 'settings' from 'settings.py'> 

Now that we have settings module in place, let’s modify our main.py:-

$ cat main.py
import os

from django.core.management import execute_from_command_line

os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

execute_from_command_line()

Now when we run python main.py, we’ll get the following:-

Type 'main.py help <subcommand>' for help on a specific subcommand.

Available subcommands:

[django]
    check
    compilemessages
    createcachetable
    dbshell
    diffsettings
    dumpdata
    flush
    inspectdb
    loaddata
    makemessages
    makemigrations
    migrate
    runserver
    sendtestemail
    shell
    showmigrations
    sqlflush
    sqlmigrate
    sqlsequencereset
    squashmigrations
    startapp
    startproject
    test
    testserver
Note that only Django core commands are listed as settings are not properly configured (error: The SECRET_KEY setting must not be empty.).

Remember, our settings.py still empty and above, Django is expecting a settings named SECRET_KEY. Django already come with list of default settings but apparently for this one, you have to specify it yourself. Let’s ignore first what the purpose of this SECRET_KEY. So fix our settings module to have that:-

$ cat settings.py
SECRET_KEY = "1+)O49,>}5!$+ 43*PN+2+=(2S'W*0^1_|76n{_"

Above, we hardcode the value of DJANGO_SETTINGS_MODULE environment variables to our settings module. Just like any environment variables, we can also specify it when we run our script:-

$ DJANGO_SETTINGS_MODULE=settings python main.py

The result would be the same. Specifying the environment variables value on the command line without hardcoding allow us to specify different settings to our app with having to modify the code. That’s one of the reason why django choose to use environment variables to store pointer to the settings. One typical usecase when you want to have different settings for development and production. Let’s continue with our app:-

$ python main.py runserver

You’ll get this error:-

CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.

So add this another settings:-

$ cat settings.py
SECRET_KEY = "1+)O49,>}5!$+ 43*PN+2+=(2S'W*0^1_|76n{_"
DEBUG = True

The error above mentioned something called ALLOWED_HOSTS but let’s ignore that for now. It’s a settings for production so it’s not applicable yet in our case. We’ll revisit that once we want to deploy our app to production server. Let’s run the app again:-

$ python main.py runserver
Performing system checks...

System check identified some issues:

WARNINGS:
?: (1_7.W001) MIDDLEWARE_CLASSES is not set.
        HINT: Django 1.7 changed the global defaults for the MIDDLEWARE_CLASSES. django.contrib.sessions.middleware.SessionMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, and django.contrib.messages.middleware.MessageMiddleware were removed from the defaults. If your project needs these middleware then you should configure this setting.

System check identified 1 issue (0 silenced).
July 21, 2015 - 14:55:21
Django version 1.8.3, using settings 'settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Now django happily start the server. Let’s try to access it. Run this on a separate console:-

$ curl http://localhost:8000/
A server error occurred.  Please contact the administrator.

You can also try to access the url using browser, the result would be similar. Let’s check the console where we run runserver:-

Traceback (most recent call last):
  File "/usr/lib/python2.6/wsgiref/handlers.py", line 93, in run
    self.result = application(self.environ, self.start_response)
  File "/home/kamal/python/dthw/django/core/handlers/wsgi.py", line 255, in __call__
    response = self.get_response(request)
  File "/home/kamal/python/dthw/django/core/handlers/base.py", line 85, in get_response
    urlconf = settings.ROOT_URLCONF
  File "/home/kamal/python/dthw/django/conf/__init__.py", line 54, in __getattr__
    return getattr(self._wrapped, name)
AttributeError: 'Settings' object has no attribute 'ROOT_URLCONF'
[11/Apr/2013 16:37:55] "GET / HTTP/1.1" 500 59

The reason django gave you an error because you haven’t tell yet django what to serve from your application. You can do this by providing a mapping between a set of url pattern to python function that will be called when the pattern match. Create a new python module, name it urls.py (once again, the name can be anything).

$ cat urls.py
from django.http import HttpResponse
from django.conf.urls import url

def hello_world(request):
    return HttpResponse('Hello world')

urlpatterns = [
    url(r'^$', hello_world),
]

The module must have a name called urlpatterns that having a list of call to function named url.

Once above is done, you can hook it into settings.py which now should look like:-

$ cat settings.py
SECRET_KEY = "1+)O49,>}5!$+ 43*PN+2+=(2S'W*0^1_|76n{_"
ROOT_URLCONF = 'urls'

ROOT_URLCONF should contain valid import path to our module that define the url mapping. Try to runserver and access our app again:-

$ python manage.py runserver

On another console:-

$ curl http://localhost:8000/

Now running the runserver and try accessing http://localhost:8000/ through browser or using curl will give the “Hello world” string. The takeout from this is that all the django need is just a function that it can call given a particular url. From that function you can do whatever you want as long as you return a valid value that is an instance of django.http.HttpResponse or it’s subclass. Another important thing to know is that most of the settings require you to provide a valid python import path that django can use to import the required module. The module itself can be anywhere and django does not restrict you to any particular structure. As long as you can do import somestuff, that would be fine. How to make sure you module can be imported will be a point of another post though.

Namespace

So far what we have been doing is defining python module in the same directory as the script that executing our application (main.py). The is the easiest to get started because nothing we have to do in order for python to be able to import our module. Most of the time python can import module or package defined in the same directory of the executing script. In our case we defined settings.py and in main.py it’s importable as settings. Similar goes to urls.py. These (settings, urls) however are too generic name that can potentially conflict with other python modules once our app grow and we need to use more python libraries than just django. Python has [namespace] to solve this so why not we start using it before getting too deep with our app.

Create a new directory called myapp (or anything you wish) in the same directory containing main.py, settings.py and urls.py.

$ mkdir myapp
$ ls
django  main.py  myapp  settings.py  urls.py

Then move settings.py and urls.py into the new directory.

$ ls
django  main.py  myapp
$ ls myapp
settings.py  urls.py

main.py should remain outside as it is the entry point to our app and it will be much easier if it is not in the containing app. This way we can phrase it as main.py will call myapp, otherwise if we put main.py in myapp, then myapp has to call itself. While technically possible it will be much harder to explain. Django has done this in the beginning and has since corrected it in last few latest versions. If you’re using python 2, In order for a directory to be recognised as valid python package (namespace), you have to provide a file named __init__.py. Most of the time it can be empty.

Now we have to fix our settings a bit to reflect the new location of our modules. It should look like this:-

$ cat myapp/settings.py
SECRET_KEY = "1+)O49,>}5!$+ 43*PN+2+=(2S'W*0^1_|76n{_"
ROOT_URLCONF = 'myapp.urls'
DEBUG = True

main.py also need fixing:-

$ cat main.py
import os

from django.core.management import execute_from_command_line

os.environ['DJANGO_SETTINGS_MODULE'] = 'myapp.settings'

execute_from_command_line()

Views

So we know that django only need to call our function for any matched url and we defined that function in the same module we defined the url mapping - urls.py. This is fine for small app but splitting it into separate module is a good practice. So urls.py can just contain url mapping instead of mixing it with our application logic. Let’s create new module to store our app function. We call it views.py but you can choose any name you like.

$ cat myapp/views.py
from django.http import HttpResponse

def hello_world(request):
    return HttpResponse('Hello world')

Inside urls.py we import the views module and hook it into our url pattern:-

from django.conf.urls import url

from myapp.views import hello_world

urlpatterns = patterns('',
    url(r'^$', hello_world),
)

Models

Now come the hardest part to explain because of some ‘hardcoding’ django did to implement the functionality. Unlike views, url config or settings, the name for the module that contain models definition was hardcoded in django - it want you to name it as models.py. Django also use models.py to imply some other parts of the framework. Let’s define our models:-

$ cat myapp/models.py
from django.db import models

class Customer(models.Model):
    name = models.CharField(max_length=255)

After defining models, you have to tell django the package that contain this models.py module. Our settings should look like:-

$ cat myapp/settings.py
$ cat myapp/settings.py
SECRET_KEY = "1+)O49,>}5!$+ 43*PN+2+=(2S'W*0^1_|76n{_"
ROOT_URLCONF = 'myapp.urls'
DEBUG = True
INSTALLED_APPS = ('myapp',)

In short, what you define in INSTALLED_APPS basically just an import path to python package that contain models.py file. You can put all your models definition for your application in a single models.py but you maybe want to split it into multiple apps for better design. A common use case is when the apps can actually being reused in other project as well. It also not necessarily must be in the same directory as your current application. What important is it can be import by the python interpreter running your application. This will always true to third party libraries that you install such as from PyPI since that libraries will be installed in some other place on your system, not in your current project directory.

Since we started to use db, we must define our database credentials settings:-

    $ cat myapp/settings.py
SECRET_KEY = "1+)O49,>}5!$+ 43*PN+2+=(2S'W*0^1_|76n{_"
ROOT_URLCONF = 'myapp.urls'
DEBUG = True
INSTALLED_APPS = ('myapp',)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'myapp.db',
    }
}

Once we configured settings.INSTALLED_APPS to have our app defined, we can run syncdb to let django create necessary database tables to store our models data:-

$ python main.py makemigrations myapp
Migrations for 'myapp':
  myapp/migrations/0001_initial.py
    - Create model Customer
$ cat myapp/migrations/0001_initial.py
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-08-21 20:07
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Customer',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=255)),
            ],
        ),
    ]
$ python main.py migrate
Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0001_initial... OK
Written on August 22, 2017 by kamal
About author

Lead Engineer at Xoxzo Inc. Develop web applications in Python/Django. Interested in open source and tech talent development.

Xoxzo.com provide API services for developer to develop messaging and voice based application. The postings on this site are my own and don't necessarily represent my employer's positions, strategies or opinions. I can be reached via Twitter @k4ml.

Ikuti perkembangan terkini melalui twitter kami @koditi atau FB Page fb.me/koditi.