One of our production Django sites broke this afternoon with a database error “relation xyz doesn’t exist”. So: a missing table.
I helped debugging it and eventually found the cause by doing a select *
from south_migrationhistory
. This lists the south migrations and lo and
behold, a migration had just been applied 25 minutes earlier. The migration
name suggested a rename of tables, which of course matches the “missing table”
error.
Cause found. But you have to ask yourself “why” again. So: “why was this migration applied?”.
Well, someone was working on a bit of database cleanup and refactoring. Naming consistency, proper use of permissions, that sort of thing. Of course, locally in a branch. And on a development database. Now why did the local command result in a migration on the production database?
So, effectively, “why don’t the development settings work as intended”? We
normally use settings.py
as the production settings and a
developmentsettings.py
that is used in development. It imports from
settings.py
and sets the debug mode and development database and so.
This project is a bit different in that there’s only a settings.py
. It
does however try to import localsettings.py
. This is generated for you
when you set up your project environment with ansible. A bit less clear (in my
opinion) than a real .py file in your github repository, but it
works. We saw the generated localsettings file with development database and
DEBUG = True
. This wasn’t the cause. What then?
Normally, calling django’s diffsettings
command (see the django
documentation)
shows you any settings errors by printing all the settings in your config that
are different from Django’s defaults. In this case, nothing was wrong. The
DATABASES
setting was the right one with the local development
database. Huh?
The developer mentioned one other thing he changed recently: importing some django signal registration module in the ``settings.py``. Ah! Django’s signals often work on the database. Yes, the signals in this module did database work, too.
So the settings.py
effectively looked like this:
DATABASES = { .... 'server': 'productiondatabase' ....}
import my_project.signal_stuff_that_works_on_the_database
try:
from .localsettings import *
# This normally sets DATABASES = { .... 'server': 'developmentdatabase' ....}
except ImportError:
pass
The import of the signal registration module apparently triggered something in
Django’s database layer so that the database connection was already
active. The subsequent change of the DATABASES
config to the local
development database didn’t have any effect anymore.
diffsettings
just shows you what the settings are and doesn’t catch the
fact that the DATABASES
isn’t really used in the form that comes out of
diffsettings.
Why the import, then?
Well, it has to be executed when django starts up. The settings file looked like a good spot. It isn’t, though.
The traditional location to place imports like this is the urls.py
or
models.py
file. That’s why the admin.autodiscover()
line is often in
your urls.py
, for instance.
So… put imports like this in models.py
or urls.py
instead of in your
settings file.
Digging even deeper… isn’t this sort of weird and ugly? Why isn’t there a more obvious place for initialization code like this? Now you have to have the arcane knowledge to somehow know where you can import and where not, right?
The answer: there is a good spot, in django 1.7. The AppConfig.ready() method! Quote from the documentation: Subclasses can override this method to perform initialization tasks such as registering signals. Bingo!
My name is Reinout van Rees and I program in Python, I live in the Netherlands, I cycle recumbent bikes and I have a model railway.
Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):