Convention Over Configuration: Django Settings

I’ve been working with Django for over five years. Even though I don’t consider myself as a programmer, one of the advantages I seem to have over less experienced Django developers is that I’ve developed (together with our team) strong conventions on how I work. This makes developing faster and generally more enjoyable.

Conventions are a tool for efficient workflow and to help read and understand code by other people better. Like so many tools, conventions too can be taken to extremes to the point when then they become magic (Rails, anyone), which in Python community is considered a bad practice.

The Python community has a name for code that feels right: that kind of code is pythonic. Good, pythonic conventions are logical and easy to remember but also explicit. In other words good conventions should make sense when you see them first time.

Django has many documented conventions (like that models should live in models.py and views in views.py) and even coding guidelines but there are many places that could use more stronger or better ones. This article is first in a series of discussing conventions related to Django. My goal is not to impose these opinnions to anyone but to share what I’ve learned while working with Django for over five years.

Conventions for Django settings

Settings are not the sharpest tool in the Django shed. This article is not about crying over the design details, but to make working with them easier.

Always use relative paths

One of the first hurdles one comes up with Django settings is that they aren’t very portable. The MEDIA_ROOT, STATIC_ROOT and TEMPLATE_DIRS settings are problematic because the given examples are absolute paths (like "/home/media/media.lawrence.com/media/"). Simply by defining your paths as relative you can free yourself from this and move your projects around without breaking your settings.

A good convention is to define a PROJECT_ROOT setting and make every path in your settings relative to that root. Django settings file is just an ordinary Python module (well, not really, but in this respect it is) so you can use Python to do this:

 import os  PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')) MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') 

Turn settings into a package

Second problem you almost immediately have is a problem of multiple settings files. You can make your life a lot easier by moving settings from ´settings.py´ into several different files in a directory called settings. (Remember that to python /path/to/your_project/settings.py and /path/to/your_project/settings/__init__.py are the same thing.)

Also, because there usually is at least two almost equal settings files (for development and production), it’s smart to differentiate common settings and the ones that change for every machine. For this, you can move common settings to common_settings.py and then just import that to your other settings files:

 from common_settings import * 

Now you can either add an empty __init__.py to the settings directory and refer to settings as DJANGO_SETTINGS_MODULE=myproject.settings.myenv or you can symlink the current environment settings as __init__.py.)

You could end up with a directory structure like this:

myproject/     manage.py     urls.py     settings/         __init__.py         common_settings.py         dev_adrian.py         dev_jacob.py         dev_simon.py         production.py         staging.py 

Now you have one file for common settings and one for each individual environment and they all can live together in version control.

Store sensitive data outside of settings files

Another problematic thing about Django settings is that you’re supposed to put things like passwords and API-keys in there. For development environment this seldomly is a problem but what about version control and different settings for staging and production environments?

At this point it is a no-brainer: just put all sensitive data to a file outside of version control and import that:

 from secrets import *

Now we don’t need to think about storing sensitive data in the version control and we just need to make sure that secrets.py is properly protected in the production environment.

Conclusion

Conventions are good and we need them. By using these few simple conventions you can make your Django projects more portable, secure and pythonic.

More important than the implemention details is that you follow some conventions. I’m a strong believer of customization and that you should make your own conventions that best fit you yourself or your team. (This, obviously, is not something you shouldn’t do when writing your first Django apps but instead when you have been working with Django a while so that you have a general feeling of what works for you and what doesn’t.)

Next in the series I’ll talk about arranging the overall project structure and the tools that help keeping your project evolving.

Meanwhile, any comments or tips that you’d like to share?

Controlling iTunes from Python

django-tunesBack in 2008 I stumbled on then new API on Mac OS X called Scripting Bridge that enables easy controlling of Cocoa apps (like iTunes) from scripting languages like Python.

I wrote simple proof-of-concept Django app that turned my iPod touch into a remote control. It was cool, even though it didn’t do very much. Now there’s of course native iOS apps for this but I was somewhat surprised that there are still not much code for controlling iTunes via Web. I misplaced my original code so I decided to write it again — just for fun.

So here’s a working class for controlling iTunes, in 58 lines of Python:

 # -*- coding: utf-8 -*- from Foundation import * from ScriptingBridge import *  class iTunes(object):     """     A helper class for interacting with iTunes on Mac OS X via Scripting     Bridge framework.      To use this, launch iTunes and make sure a playlist or an album is ready.      Usage:      >>> player = iTunes()     >>> player.status     'playing'     >>> player.current_track     u'Maison Rilax'     >>> player.current_album     u'Maison Rilax'     >>> player.current_artist     u'Lemonator'     >>> player.pause()     >>> player.status     'paused'     >>> player.play()     >>> player.next()     >>> player.current_track     u'Not Your Game'      """      def __init__(self):         self.app = SBApplication.applicationWithBundleIdentifier_("com.apple.iTunes")      def _get_status(self):         if self.app.playerState() == 1800426320:             return "playing"         elif self.app.playerState() == 1800426352:             return "paused"         else:             return "unknown"     status = property(_get_status)      def _get_current_track(self):         return self.app.currentTrack().name()     current_track = property(_get_current_track)      def _get_current_artist(self):         return self.app.currentTrack().artist()     current_artist = property(_get_current_artist)      def _get_current_album(self):         return self.app.currentTrack().album()     current_album = property(_get_current_album)      def _set_volume(self, level):         """         level should be an integer between 0-100.         """         self.app.setSoundVolume_(level)      def _get_volume(self):         return self.app.soundVolume()     volume = property(_get_volume, _set_volume)      def pause(self):         self.app.pause()      def play(self):         # According to AppleScript documentatin there should be a .play()         # method, but apparently there isn't. So we fake it :)         if self.status == "paused":             self.app.playpause()      def next(self):         self.app.nextTrack()      def previous(self):         self.app.previousTrack() 

This could be easily refined into something potentially interesting like a native REST API for iTunes. The downside, of course, is that it only works on a Mac. (The code is available also on BitBucket.)

A somewhat related is a project called Mopidy, which is a Python powered MPD music server for the awesome Spotify music service (that is suposed to finally be launching in US very soon, I hear.). I’m not at all familiar with MPD but I think it wouldn’t be very difficult to build a Django frontend to a server like Mopidy to get a very slick Web-based interface to your music. Now that would be cool.

Time to Wake Up

It’s been too long since last entry in this blog. I’ve had lots of good and bad excuses, but I’ve also collected a healthy pile of ideas for future topics, like “how to install Django on Mac OS X with just one command” and “best practices for installing dev environment on Mac OS X Lion”.

Just to get started with installing Django on Mac OS X, it’s as easy as saying sudo easy_install django in the Terminal.app. But there’s more to it. We’ll get to that in another post.

And for the record, this blog was never dead, it just took a three year vacation.