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.









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.