Skip to main content

A Django buildout recipe

Project description

This recipe allows you to setup a Django project through zc.buildout.

Usage

The main scope of the recipe is to abstract out the settings.py file, allowing settings to reside inside the buildout instead of having them reside into code (leading to an awkard handling of the same in respect to versioning, for example). The settings.py file is generated by a template, either the default one, the default one and a user extension, or a totally new one.

The template uses the Tempita templating system.

The most basic usage of this recipe is as follows:

[buildout]
parts = django

[django]
recipe = djc.recipe
project = my.project

Where my.project is an importable package containing a urls module and a templates directory.

As you see, very few options are specified here: the defaults are used to build up the settings.py file.

Of course, real examples tend to be slightly more complex: for example here is a buildout used for Satchmo:

[buildout]
parts =
    omelette

find-links =
    http://dist.plone.org/thirdparty/

versions = versions

eggs =
    PIL
    pysqlite
    Django
    pycrypto
    PyYAML
    django-registration
    django-livesettings
    django-keyedcache
    django-signals-ahoy
    django-caching-app-plugins
    django-threaded-multihost
    sorl-thumbnail
    trml2pdf
    ReportLab
    Satchmo
    example.site

[django]
recipe = djc.recipe.django
project = example.site
settings-template-extension = templates/settings-extension.py.in
media-origin = satchmo_store:../static
media-url = static
admin-media = media
template-loaders =
    django.template.loaders.filesystem.load_template_source
    django.template.loaders.app_directories.load_template_source
middleware =
    django.middleware.common.CommonMiddleware
    django.contrib.sessions.middleware.SessionMiddleware
    django.middleware.locale.LocaleMiddleware
    django.contrib.auth.middleware.AuthenticationMiddleware
    django.middleware.doc.XViewMiddleware
    threaded_multihost.middleware.ThreadLocalMiddleware
    satchmo_store.shop.SSLMiddleware.SSLRedirect
template-context-processors =
    satchmo_store.shop.context_processors.settings
    django.core.context_processors.auth
apps=
    django.contrib.sites
    satchmo_store.shop
    django.contrib.admin
    django.contrib.auth
    django.contrib.contenttypes
    django.contrib.comments
    django.contrib.sessions
    django.contrib.sitemaps
    registration
    sorl.thumbnail
    keyedcache
    livesettings
    l10n
    satchmo_utils.thumbnail
    satchmo_store.contact
    tax
    tax.modules.no
    tax.modules.area
    tax.modules.percent
    shipping
    product
    payment
    payment.modules.dummy
    satchmo_ext.satchmo_toolbar
    satchmo_utils
    app_plugins
    product.modules.configurable
authentication-backends =
    satchmo_store.accounts.email-auth.EmailBackend
    django.contrib.auth.backends.ModelBackend
languages =
    en English
    it Italiano
language-code = en-us
timezone = Europe/Rome
site-name = Satchmo Sample Site
site-domain = localhost
site-id = 1
debug = true

[versions]
PIL = 1.1.7
trml2pdf = 0.1
Satchmo = 0.9-1

As you can see, a lot more options are used, including an extension template (the default template with appended the given template).

See Options, Default template options and Example usage for more details.

Detailed Documentation

Options

The options of this recipe are not fixed, as many of those are used exclusively within the settings template file (see Templating).

Here we present the options that have an impact on the recipe aswell:

project

This identifies a python module (in dotted notation) that can serve as project package. The bare minimum for a project package is to contain a urls.py file and a templates directory. It is mandatory unless both the urlconf and templates option are defined.

urlconf

Identifies the module that contains the url definition: if omitted the file urls.py inside the module given as project is used.

templates

Identifies the templates directory. If omitted, the directory named templates located in the module given as project is used.

media-directory

Identifies the folder into which static content (images, CSS and Javascripts) will go. Relatives path are considered relative to the buildout directory. The directory will be created if not present, and nothing will be done if it already exists. If omitted, defaults to static.

settings-template

If specified, the given template is used to generate the settings.py file, if not provided, the default template will be used. See Templating for more details.

settings-template-extension

If specified, the given template is appended to the template specified by settings template or to the default one.

media-origin

If specified, defines directories from which to copy the static files that have to go in media-directory: see Media origin for more details.

base-settings

A settings module (only absolute imports) that is extended by the current settings. If specified, the defaults for apps, middleware and template-loaders becomes an empty string (resulting into them not being written at all).

wsgi

Defaults to false. If set to true (or on or 1) creates a script in parts/$partname named $partname.wsgi.py that can be used as WSGI script in Apache or other WSGI enabled webserver.

Advanced options

The following advanced options are supported:

extra-paths

A number of non-standard paths where additional python modules are located.

pth-files

A number of pth-files from which to load additional python modulesthat should be present in the buildout.

Templating

The settings.py file is generated by interpolating the options of the buildout section with a template, be it the default one or the one provided by the settings-template option.

The template must be a valid Tempita template, to which the whole options of the current buildout section is passed as namespace, integrated as follow:

  1. In the options name, all minuses (-) are converted to underscores (_)

  2. The option name and secret are added, respectively mapping to the buildout section name and to a randomly-generated secret [1].

  3. A serie of functions is added to the namespace to simplify the handling of some situations, see below for more details.

Functions

A certain number of functions can be used inside the templates:

absolute_url

Takes a path and, if it is relative, concatenates it with the buildout location to make it absolute.

listify

Takes a chunk of data, splits it into lines, trims those lines and returns the obtained list, from which void strings are purged.

rfc822tuplize

This function is quite specialized and takes any string in the form Full Name <email.address@example.com> into a tuple composed by the full name and the mail address. It will return a tuple with the unchanged data if the data fed in does not conform to the specifics.

boolify

This functions returns True if the data fed is is any of true, on, 1 (case- insensitive) and False otherwise

join

Equivalent of string’s join() method, with the data to join as first parameter, the infix as second and two optional parameters prefix (added just one to the beginning) and suffix (added just one to the end)

Default template options

The default template accepts a number of options. They are to be considered all optional, as sensible defaults will be provided if omitted.

media-url

The static content prefix path. Defaults to media

admin-media

The admin only static content prefix path. Defaults to admin_media

database-engine

The database engine to use: defaults to sqlite

database-name

The name of the database to use: defaults to storage.db

database-user

The username to use when connecting to the database server. Defaults to empty string.

database-password

The password to use when connecting to the database server. Defaults to empty string.

database-host

The host on which the database server resides. Defaults to empty string.

database-port

The port on which the database server accepts connections. Defaults to empty string.

timezone

The timezone: defaults to America/Chicago

language-code

The language code: defaults to en-us

admins

The list of site admins, in RFC822 form. Defaults to John Smith <root@localhost>

managers

The list of managers: same as for admins. Defaults to copy the value of admins

middleware

The list of middleware classes to load. If an empty string, the value is not written at all.

apps

The list of apps to load. If empty, the value is not written at all.

template-loaders

The list of template loaders to use. If empty, the value is not written at all.

debug

If true, activates debug mode. Defaults to false

site-id

The Django site id. Defaults to unset.

template-context-processors

The Django template context processors. Defaults to unset.

authentication-backends

The Django authentication backends. Defaults to unset

languages

A list of supported languages in the form code Fullname, for example en-us English (US). Defaults to unset.

smtp-host

The SMTP host to use when sending mail. Defaults to localhost.

smtp-port

The SMTP server port. Defaults to 25.

smtp-user

The username to use to connect to the SMTP server. Defaults to unset.

smtp-password

The password to use to connect to the SMTP server. This is not valid if smtp-user is not set aswell. Defaults to unset.

smtp-tls

Whether TLS should be used when connecting to the SMTP server (boolean option). Defaults to false.

site-domain

The site domain. Defaults to unset.

site-name

The site title. Defaults to unset.

cache-backend

The cache backend. Defaults to locmem:///.

cache-timeout

The cache timeout in seconds. Defaults to 60*5.

cache-prefix

The cache prefix (prefixed at all cache IDs). Defaults to Z.

fixture-dirs

The directories into which search for fixtures. Not set by default.

Example usage

As first thing, we need to have a Django project egg around. We have made a very simple one just for testing and we have created a source distribution for it located in packages.

This is of course not the only way you can distribute and obtain the project egg: for example, during developement, it is recommended to use mr.developer for that.

That cleared, we create the most simple buildout conceivable using this recipe

>>> write('buildout.cfg',
... """
... [buildout]
... parts = django
... offline = false
... index = http://pypi.python.org/simple/
... find-links = packages
...
... [django]
... recipe = djc.recipe
... project = dummydjangoprj
... """)

And run it

>>> print "start\n", system(buildout)
start
...
Installing django.
django: Specified project 'dummydjangoprj' not found, attempting install
Getting distribution for 'dummydjangoprj'.
...
django: Generating settings in ...
django: Making empty media directory ...
django: Creating script at ...
Generated script ...
<BLANKLINE>

This generated some files and directories for us:

  1. A Django manage.py wrapper located at bin/django

  2. A media directory (empty) at static (default option)

  3. A settings file located in parts/django/settings.py

So, as we can see, we have a static directory in the root, a bin/django script and a parts/django part

>>> ls(sample_buildout)
-  .installed.cfg
-  .secret.cfg
d  bin
-  buildout.cfg
d  develop-eggs
d  eggs
d  packages
d  parts
d  src
d  static
>>> ls('bin')
-  buildout
-  django
>>> ls('parts')
d  django

Let’s look at this first

>>> ls('parts', 'django')
-  settings.py
>>> cat('parts', 'django', 'settings.py')
ADMINS = (
<BLANKLINE>
    ('John Smith', 'root@localhost'),
)
MANAGERS = ADMINS
<BLANKLINE>
<BLANKLINE>
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'storage.db'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''
<BLANKLINE>
TIME_ZONE = 'America/Chicago'
<BLANKLINE>
LANGUAGE_CODE = 'en-us'
<BLANKLINE>
MEDIA_ROOT = '.../static'
<BLANKLINE>
MEDIA_URL = '/media/'
<BLANKLINE>
ADMIN_MEDIA_PREFIX = '/admin_media/'
<BLANKLINE>
SECRET_KEY = '...'
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.middleware.doc.XViewMiddleware',
)
<BLANKLINE>
ROOT_URLCONF = 'dummydjangoprj.urls'
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.admin',
)
<BLANKLINE>
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
)
<BLANKLINE>
TEMPLATE_DIRS = (
    '.../dummydjangoprj/templates',
)
<BLANKLINE>
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_USE_TLS = False
<BLANKLINE>
CACHE_BACKEND = 'locmem:///'
CACHE_TIMEOUT = 60*5
CACHE_PREFIX = 'Z'

As you can see, this is pretty much the standard Django settings.py as created by Django’s django-admin. It has the peculiarity of not residing in a module, however, but is loaded at run time into the appropriate manage script as a ghost module named _django_settings.

Let’s have a look at the manage script

>>> cat('bin', 'django')
#!...
<BLANKLINE>
import sys
sys.path[0:0] = [
  ...
  ]
<BLANKLINE>
import djc.recipe.manage
<BLANKLINE>
if __name__ == '__main__':
    djc.recipe.manage.main('.../parts/django/settings.py')

As we can see, the main() function of the manage module is called, passing in the file with the settings as only argument.

Template overriding

As it was said in Templating, the default template can be overridden or extended.

Let’s start by extending it:

>>> write('template-extension.py.in',
... """
... # Here we can extend the template, using variables pulled in from the
... # buildout section, with the dashes converted to underscores
... MY_CONFIG_VARIABLE = '{{config_variable_one}}'
... """)
>>> write('buildout.cfg',
... """
... [buildout]
... parts = django
... offline = false
... index = http://pypi.python.org/simple/
... find-links = packages
...
... [django]
... recipe = djc.recipe
... project = dummydjangoprj
... settings-template-extension = template-extension.py.in
... config-variable-one = test
... """)

Launch the buildout and then take a look at the generated settings.py file

>>> print system(buildout)
Uninstalling django.
Installing django.
...
Generated script ...
<BLANKLINE>
>>> cat('parts', 'django', 'settings.py')
ADMINS = (
<BLANKLINE>
    ('John Smith', 'root@localhost'),
)
MANAGERS = ADMINS
<BLANKLINE>
<BLANKLINE>
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'storage.db'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''
<BLANKLINE>
TIME_ZONE = 'America/Chicago'
<BLANKLINE>
LANGUAGE_CODE = 'en-us'
<BLANKLINE>
MEDIA_ROOT = '.../static'
<BLANKLINE>
MEDIA_URL = '/media/'
<BLANKLINE>
ADMIN_MEDIA_PREFIX = '/admin_media/'
<BLANKLINE>
SECRET_KEY = '...'
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.middleware.doc.XViewMiddleware',
)
<BLANKLINE>
ROOT_URLCONF = 'dummydjangoprj.urls'
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.admin',
)
<BLANKLINE>
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
)
<BLANKLINE>
TEMPLATE_DIRS = (
    '.../dummydjangoprj/templates',
)
<BLANKLINE>
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_USE_TLS = False
<BLANKLINE>
CACHE_BACKEND = 'locmem:///'
CACHE_TIMEOUT = 60*5
CACHE_PREFIX = 'Z'
<BLANKLINE>
<BLANKLINE>
# Extension template %s
<BLANKLINE>
<BLANKLINE>
# Here we can extend the template, using variables pulled in from the
# buildout section, with the dashes converted to underscores
MY_CONFIG_VARIABLE = 'test'

As you can see, the aditional template has been simply appended to the default, and the variable config-variable-one has been substituted.

If, instead, we totally override the template:

>>> write('template.py.in',
... """
... # Total override
... FOODS = (
...     {{join(listify(foods), "',\\n    '", "'", "',")}}
... )
... """)
>>> write('buildout.cfg',
... """
... [buildout]
... parts = django
... offline = false
... index = http://pypi.python.org/simple/
... find-links = packages
...
... [django]
... recipe = djc.recipe
... project = dummydjangoprj
... settings-template = template.py.in
... foods =
...     spam
...     spam
...     eggs
...     spam
... """)

Launch the buildout and then take a look at the generated settings.py file

>>> print system(buildout)
Uninstalling django.
Installing django.
...
Generated script ...
<BLANKLINE>
>>> cat('parts', 'django', 'settings.py')
# Total override
FOODS = (
    'spam',
    'spam',
    'eggs',
    'spam',
)

As you can see, the builtin template has been totally discarded.

Media origin

Static files are generally not served through Django, but instead the front-end web server takes care to serve them by exposing a directory on the filesystem to the web.

However, many static files (think .js or .css) are part of the functionality of a project or application, and would be interesting to be able to distribute them alongside the code.

The relevant resources can be included in the distributed package and use of the media-origin option will allow them to be copied into the media-directory folder (see Options).

media-origin can contain a list of static file sources, and each item of the list can be either in the form package:directory or package:directory:destination; package being the full dotted name of the importable module, directory the path to the directory inside the module containing static data, and destination an optional subdirectory inside media-directory where to copy the files.

Let’s then begin from the first, simple case, with a single source of static data.

The source of static data is the package dummydjangoapp1, residing as a developement package inside src.

>>> ls('src', 'dummydjangoapp1', 'dummydjangoapp1', 'static')
-  lib1.js
-  main.css
>>> cat('src', 'dummydjangoapp1', 'dummydjangoapp1', 'static', 'main.css')
body { font-family: "Helvetica" "Arial" sans-serif; }

Let’s create a buildout config and run it

>>> write('buildout.cfg',
... """
... [buildout]
... parts = django
... offline = false
... index = http://pypi.python.org/simple/
... find-links = packages
... develop = src/dummydjangoapp1
... eggs = dummydjangoapp1
...
... [django]
... recipe = djc.recipe
... project = dummydjangoprj
... media-directory = static
... media-origin = dummydjangoapp1:static
... """)
>>> rmdir('static')
>>> print system(buildout)
Develop: '.../dummydjangoapp1'
...
Uninstalling django.
Installing django.
...
django: Making media directory '.../static'
...
Generated script ...
<BLANKLINE>

And now let’s see what’s in static

>>> ls('static')
-  lib1.js
-  main.css
>>> cat('static', 'main.css')
body { font-family: "Helvetica" "Arial" sans-serif; }

Let’s now try using two sources: the second is another dummy app, named dummydjangoapp2, that like the first one resides in src.

Let’s see what’s in its static for us:

>>> ls('src', 'dummydjangoapp2', 'dummydjangoapp2', 'static')
-  lib2.js
-  main.css

It seems this app too defines a main.css, so let’s look at the content:

>>> cat('src', 'dummydjangoapp2', 'dummydjangoapp2', 'static', 'main.css')
h1 { color: #92B8D8; }

But this poses a problem! What happens when I put this as second source, and both define main.css? Well, the intuitive thing to do here is probably to override the file, so that the source at the bottom is the top skin layer.

So if we have this buildout

>>> write('buildout.cfg',
... """
... [buildout]
... parts = django
... offline = false
... index = http://pypi.python.org/simple/
... find-links = packages
... develop =
...     src/dummydjangoapp1
...     src/dummydjangoapp2
... eggs =
...     dummydjangoapp1
...     dummydjangoapp2
...
... [django]
... recipe = djc.recipe
... project = dummydjangoprj
... media-directory = static
... media-origin =
...     dummydjangoapp1:static
...     dummydjangoapp2:static
... """)

It is reasonable to expect that, after running it, the content of the main.css file is the one provided by the version held by dummydjangoapp2 rather than the one held by dummydjangoapp2.

A quick run and inspect confirms this:

>>> rmdir('static')
>>> print system(buildout)
Develop: '.../dummydjangoapp1'
...
Uninstalling django.
Installing django.
...
django: Making media directory '.../static'
...
Generated script ...
<BLANKLINE>
>>> ls('static')
-  lib1.js
-  lib2.js
-  main.css
>>> cat('static', 'main.css')
h1 { color: #92B8D8; }

However, I might not want the main.css override to happen, or any other clash between applications, for that matter. That is easily solved by a buildout written like this

>>> write('buildout.cfg',
... """
... [buildout]
... parts = django
... offline = false
... index = http://pypi.python.org/simple/
... find-links = packages
... develop =
...     src/dummydjangoapp1
...     src/dummydjangoapp2
... eggs =
...     dummydjangoapp1
...     dummydjangoapp2
...
... [django]
... recipe = djc.recipe
... project = dummydjangoprj
... media-directory = static
... media-origin =
...     dummydjangoapp1:static:app1
...     dummydjangoapp2:static:app2
... """)

It is to be noticed that the media-origin values have now three elements, the latter being the destination directory, which is defined as a subdirectory of static: in this case, both apps live in their subdirectory and no clash happens

>>> rmdir('static')
>>> print system(buildout)
Develop: '.../dummydjangoapp1'
...
Uninstalling django.
Installing django.
...
django: Making media directory '.../static'
...
Generated script ...
<BLANKLINE>
>>> ls('static')
d  app1
d  app2
>>> ls('static', 'app1')
-  lib1.js
-  main.css
>>> cat('static', 'app1', 'main.css')
body { font-family: "Helvetica" "Arial" sans-serif; }
>>> ls('static', 'app2')
-  lib2.js
-  main.css
>>> cat('static', 'app2', 'main.css')
h1 { color: #92B8D8; }

Of course, this behaviour is not usefol only in this case: an application might actually require you to put the static files in a precise subdirectory irrespective of the fact that other apps might be present or a clash occur.

Contributors

Inital developement sponsored by Abstract Open Solutions

Change history

0.3 (2010-05-19)

  • Added multiple media-origin support [Simone Deponti]

  • Added fixture-dirs support [Simone Deponti]

0.2 (2010-05-17)

  • First public release [Simone Deponti]

0.1 (2010-04-22)

  • Created package [Simone Deponti]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

djc.recipe-0.3.tar.gz (29.8 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page