Proudly ProcrasDonating

Technology Thoughts

Django Tricks, Part 5 – Automatic App Settings

This post is the fifth in a series on Django tricks.

We started with automating Admin Model creation, and continued with automating initialization and installation, model mixins and forms.

This post describes how we automate incorporating new Django apps into our project.

Creating apps is a wonderful way to modularize work. Encapsulation, abstraction, re-use!

To reduce copy/paste/rewrite drudger we’ve automated our project’s settings.py file according to standard Django conventions.

APPS list

First, we need to list what apps are active in the project:

APPS = ('procrasdonate', 'crosstester', 'adwords', 'procrasdocoder')

Pathify utility

Let’s setup some convenience path functions.

path converts a path with unix path separator, /, into a path with the appropriate os-specific path separator.

pathify converts a list of path components into a string path with appropriate path separator.

Finally, PROJECT_PATH holds the projects root absolute path.

import os def path(p):     """     @param p: path with unix path separator     @return: string with os-specific path separator     """     return os.sep.join(p.split("/")) import re _space_replace = re.compile("([^\\\])( )") def pathify(lst):     """     @param lst: list of path components     @return: string with os-specific path separator between components     """     # replaces spaces with raw spaces so that spaces in file names work     repl = r"\g<1> "     return os.sep.join([_space_replace.sub(repl, el) for el in lst]) PROJECT_PATH = os.path.dirname(os.path.realpath(__file__))

Middleware

Now we’re into the core of the work, which is to iterate through each app and append the appropriate path or module to the relevant tuple.

MIDDLEWARE_CLASSES = (     'django.middleware.doc.XViewMiddleware',     'django.middleware.common.CommonMiddleware',     'django.contrib.sessions.middleware.SessionMiddleware',     'django.contrib.auth.middleware.AuthenticationMiddleware',     'ext.pagination.middleware.PaginationMiddleware',     'django.contrib.csrf.middleware.CsrfMiddleware',     'lib.ssl_middleware.SSLRedirect', ) for app in APPS:     if os.path.exists(pathify([PROJECT_PATH, app, 'middleware.py'], file_extension=True)):         MIDDLEWARE_CLASSES += (             '%s.middleware.%sMiddleware' % (app, app.capitalize()),         )

Templates

Add template directories.

Note: Do you see how we also add the template directory of our Firefox extension? This is so that our server can reuse extension templates (OMGSQUEEL).

TEMPLATE_DIRS = (     #'procrasdonate/ProcrasDonateFFExtn/content/templates',     pathify([PROJECT_PATH, path('procrasdonate/ProcrasDonateFFExtn/content/templates')]), ) for app in APPS:     if os.path.exists(pathify([PROJECT_PATH, app, 'templates'])):         TEMPLATE_DIRS += (             pathify([PROJECT_PATH, path('%s/templates' % app)]),         )

Installed Apps

INSTALLED_APPS = (     'django.contrib.contenttypes',     'django.contrib.sessions',     'django.contrib.sites',     'django.contrib.humanize',     'ext.pagination',     'django.contrib.auth',     'django.contrib.admindocs',     'django.contrib.admin', ) for app in APPS:     INSTALLED_APPS += (         app,     )

Context Processors

TEMPLATE_CONTEXT_PROCESSORS = (     'lib.context.defaults',     'django.core.context_processors.auth',     'django.core.context_processors.debug',     'django.core.context_processors.i18n',     'django.core.context_processors.media',     'django.core.context_processors.request', ) for app in APPS:     if os.path.exists(pathify([PROJECT_PATH, app, 'context.py'], file_extension=True)):         TEMPLATE_CONTEXT_PROCESSORS += (             '%s.context.defaults' % app,         )

This only works if the app includes a “context.py” file with a function called “defaults” that returns the context dictionary:

context.py

def defaults(request):     frame = {}     frame.update(useful_settings())     frame.update(header_data())     ...     return frame def useful_settings():     return { 'PDVERSION': settings.PDVERSION } def header_data():     return { ... }

Urls

The project’s base URL file is modified to automatically incorporate each app’s URLs.

For example, an app called “magic_cape” would be accessible from the URL “/magic_cape/”, assuming it contained a “urls.py” file inside a “views” directory.

To customize the urls for an app, it’s name is added to the CUSTOM_URLS_APPS tuple.

CUSTOM_URLS_APPS = ('procrasdonate',) for app in settings.APPS:     if app not in CUSTOM_URLS_APPS and os.path.exists(pathify([settings.PROJECT_PATH, app, 'views'])):         urlpatterns += patterns('',             (r'^%s/' % app, include('%s.views.urls' % app)),         )

Proudly ProcrasDonating,

Lucy.

3 Comments »

  Matthew Schinckel wrote @

You rely on there being files of a particular name, whereas it would be sounder to import them as a module. That way, when a project grows beyond one context processor, for instance, it can be in a context/ directory.

I already do this with models, views and admin, which usually have multiple classes in each, even for quite simple apps.

This greatly reduces the size of each file, making it easier to find things, and to have more atomic checkins to version control.

  Lucy wrote @

Mike, thanks for all the feedback here and on github! I’m checking out your code now to learn how you do things.

I’m not exactly sure what you mean about multiple files. I often use multiple files for models, views and urls. Even putting models into a model folder should still be importable by using __init__ imports so that it works with Django.

The context processor is the only thing I’ve ever kept to one file, so that’s why it’s rather stupid for including a default context class. Good point, there—maybe I’m not taking advantage of context processors like I should be?

It is true that this “design pattern” requires laying out each app in a particular way with particular file names (urls.py, templates/, models.py). Its a double edged sword as with any design pattern in terms of learning curve and then familiarity. Extreme app variations can of course be added by hand, and the system improved as necessary. I remember being excited about the process of automation—hey, you can do this in python (easily)!—and that’s what I wanted to share.

  Matthew Schinckel wrote @

The line in question is:

if os.path.exists(pathify([PROJECT_PATH, app, 'middleware.py'], file_extension=True)):

Rather than look for a file that exists, try importing, and examining the exception that is raised if any. Then you can often work out if it is an error in the file, an re-raise, or ignore. This is how the admin uses its autodiscover feature.

As for context processors: I haven’t used them much yet. They are something I probably should look at.

Matt.


Your comment

HTML-Tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>