I don't really blog anymore. Click here to go to my main website.

muhuk's blog

Nature, to Be Commanded, Must Be Obeyed

September 24, 2009

Django Fixtures

A fixture is basically a data dump in a specific format. There is no restriction of which models or how much data a fixture can contain. Fixtures are portable. They work the same with all the database backends and operating systems supported by Django. This means that you can use fixtures as a simple data migration tool[1]. Django supports a number of formats by default. JSON format is widely used in the community. But you can easily create serializers for other formats. Although it is not a requirement, all built-in serializers produce human-readable output. Therefore fixtures are an ideal way to keep your data and code together without tightly coupling them.

One limitation you should be aware of is that fixtures are deserialized with the absolute values of primary keys. This makes it difficult to store permissions in fixtures. Also, say, you have two different fixtures for the same model with overlapping pk values. If you load both, instances with overlapping pk’s will be overwritten by the second load.

Test Fixtures

Fixtures are most useful during testing. They are an easy, reliable way to test your app with some data. If you prefer unittests like I do, here’s how to attach fixtures to your TestCase:

from django.test import TestCase

class MyTestCase(TestCase):
    fixtures = ['test_users', 'test_foos.json', 'test_bars.xml']

    # rest of your class

Here is a tip for preparing fixtures quickly:

  1. Enable admin for your app. Just registering your models should be enough.
  2. Create and/or edit test data via admin.
  3. You can also use Django shell[2] for test data creation.
  4. Dump your data as intermediary fixtures[3].
  5. Manually perform any final editing if necessary and save.

This method might look like a lot of work. But it is actually very practical to prepare your test fixtures, or fixtures of any kind this way. Most of the work is removing unneeded data and tweaking the primary keys in the final step. But it is still faster than manually writing the whole thing.

Initial Data

If you want to load some data right after creating your database tables, you provide initial_data fixtures. At the end of syncdb (and flush), Django automatically loads fixtures in your installed apps[4] named initial_data regardless of their format.

This is good if your app require some data to function correctly. But there is a pitfall; as stated in Django documentation your initial_data fixture will be loaded every time you run syncdb. And if you edit these entries they will be overwritten next time you run syncdb. Therefore initial_data is only good for data immutable in nature, such as units of length.

If you want to supply initial data for your project it is better to create one or more bootstrap fixtures and load them manually. Here is my minimal list of things I put in my bootstrap fixture:

  • An auth.User as my superuser. It is much easier to load a superuser from fixtures than to create it interactively. I always run syncdb with --noinput and just change my superuser’s password once when I deploy.
  • A profile for my superuser.
  • A sites.site object.

And of course if all of the following fixtures will be loaded when you issue manage.py loaddata bootstrap command:

  • project_dir/myapp1/fixtures/bootstrap.json
  • project_dir/myapp1/fixtures/bootstrap.xml
  • project_dir/myapp2/fixtures/bootstrap.json
  • project_dir/myapp3/fixtures/bootstrap.yml

I think it would be nice if re-usable app authors agreed upon a convention like naming initial data fixtures bootstrap.

[1]For very little amount of data this works fine. But serialization/deserialization becomes unreasonably slow for larger data sets. Then it’s better to use native tools for your database, modifying the input if necessarily.
[2]Use manage.py shell command to enter Django shell.
[3]Use manage.py dumpdata <app_name> > <out_file> command to dump your app data as a fixture. By default Django uses JSON format. If you add --indent=2 it will make the output much easier to read and edit.
[4]Apps that are in your settings.INSTALLED_APPS.

If you have any questions, suggestions or corrections feel free to drop me a line.