Oct 07

Hospex tutorial: starting with django

Django project layout

A simple django project layout consists of not much more than a few files:

  • urls.py
  • manage.py
  • settings.py

and maybe an app that consists of the following files:

  • models.py
  • views.py

Although there is a little bit of magic in django file naming and structure, it's all python, and as long as you follow the few conventions that django needs to function, you can order them in standard python modules and libraries than can live anywhere in your filesystem as long as the directories holding them are defined in your PYTHON_PATH.

Actually, you want to create a project that consists of small self-contained, generic and reusable components, so trying to keep your actual django project as small, lean (and empty) as possible is one way of achieving this. Anything that can be used generically is spinned off as it's own resuable app and lives outside of the project structure. We will have a folder called apps for our project specific apps, and a templates folder for our basic html templates. The folder static will hold our static media files, and I will be splitting the settings file in parts and placing them in the settings folder.

Furthermore, during development and for deployment, we want our project to be a python package, that we can install using pip, a tool for installing and managing Python packages. We achieve this by structuring our project in a certain way, and adding a setup.py file. We will add a bin folder for custom scripts, so if the root folder of our project is called django-hospex, our initial result will look like this:

django-hospex/
- hospex/
 -- apps/
 -- bin/
 -- -- manage.py
 -- settings/
 -- static/
 -- tempates/
 -- urls.py
 -- settings.py
 --  __init__.py
 - setup.py
 - fabfile.py
 - __init__.py

In our case our setup.py file does little more than define some metadata and install the scripts under bin. You can do more in the setup.py file, like configure the metadata that is needed when you want to upload a python package to pypi, the python package repository.

The fabfile will hold our development and deployment fabric commands, used to automate common tasks.

Notice we moved the manage.py file under bin/. I learned this trick from lincolnloop, a cool django shop that have been opensourcing a lot of there know-how. Once we install our project using pip, the manage.py file will be placed on the system PATH and you will be able to call it from anywhere. We will be using virtualenvs to contain python environments, so having several installed won't be a problem, but more on that later.

Let's take a look at the setup.py file:

from setuptools import setup, find_packages

setup(name='django-hospex',
     version = "0.1",
     packages=find_packages(),
     exclude_package_data={'rh2': ['bin/*.pyc']},
     setup_requires = ["setuptools_git >= 0.3",],
     scripts=['hospex/bin/manage.py'])

That's all what is needed to turn a set of python modules into a package. We call setup from setuptools with our project metadata, and tell it where to find the files it needs to include using find_packages. We include setuptools_git, as it helps find_packages find what it needs when you are using git.

We also modify the manage.py script that comes with django out of the box, enabling it to find settings (as manage.py now resides in ./bin (if you have skipped the django tutorials, don't worry, you will see what we use manage.py for later):

##!/usr/bin/env python
import os
import sys
from django import get_version
from django.core.management import execute_from_command_line, LaxOptionParser
from django.core.management.base import BaseCommand

# Work out the project module name and root directory, assuming that this file
# is located at [project]/bin/manage.py
PROJECT_DIR, PROJECT_MODULE_NAME = os.path.split(
            os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

# Check that the project module can be imported.
try:
    __import__(PROJECT_MODULE_NAME)
except ImportError:
    # Couldn't import the project, place it on the Python path and try again.
    sys.path.append(PROJECT_DIR)
    try:
        __import__(PROJECT_MODULE_NAME)
    except ImportError:
        sys.stderr.write("Error: Can't import the \"%s\" project module." %
                         PROJECT_MODULE_NAME)
        sys.exit(1)

def has_settings_option():
    parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
                             version=get_version(),
                             option_list=BaseCommand.option_list)
    try:
        options = parser.parse_args(sys.argv[:])[0]
    except:
        return False # Ignore any option errors at this point.
    return bool(options.settings)

if not has_settings_option() and not 'DJANGO_SETTINGS_MODULE' in os.environ:
    settings_module = '%s.settings' % PROJECT_MODULE_NAME
    os.environ['DJANGO_SETTINGS_MODULE'] = settings_module

execute_from_command_line()

Good! Now that we have a simple folder structure, let's go on to creating a git repository.