Welcome | Get started | Dive into Lino | Contribute | Topics | Reference | More

Writing Python fixtures

This tutorial shows how to use the Python serializer for writing and loading demonstration data samples for application prototypes and test suites.

We suppose that you have followed the Create your first Lino site tutorial.

Introduction

You know that a fixture is a collection of data records in one or several tables which can be loaded into a database. Django's Providing initial data for models article says that "fixtures can be written as XML, YAML, or JSON documents". Well, Lino adds another format to this list: Python.

Here is a fictive minimal example of a Python fixture:

from myapp.models import Foo
def objects():
    yield Foo(name="First")
    yield Foo(name="Second")

A Python fixture is a normal Python module, stored in a file ending with .py and designed to being imported and exectued during Django's loaddata command. It is furthermore expected to contain a function named objects which must take no parameters and which must return (or yield) a list of database objects.

Writing your own fixture

Create a directory named fixtures in your local project directory (the one you created in Create your first Lino site):

$ cd ~/lino/lino_local/hello
$ mkdir fixtures

Create an empty file __init__.py in that directory to mark is as a package:

$ touch fixtures/__init__.py

Create a file dumpy1.py in that directory with the following content, but don't hesitate to put your real name and data, this is your local file.

 1from lino.api import dd, rt
 2
 3
 4def objects():
 5    User = rt.models.users.User
 6    yield User(username='pbommel',
 7               first_name='Piet',
 8               last_name='Bommel',
 9               email='piet@bommel.be')
10
11    yield User(username='jdupond',
12               first_name='Jean',
13               last_name='Dupond',
14               email='jean@bommel.be')

Initialize your database using this fixture:

$ python manage.py initdb dumpy1

The output should be as follows:

>>> shell("python manage.py initdb dumpy1 --noinput")
... 
`initdb dumpy1` started on database .../default.db.
...
Loading data from .../dumpy1.py
Installed 2 object(s) from 1 fixture(s)

Let's use the show command to see whether our data has been imported:

$ python manage.py show users.Users

The output should be as follows:

>>> shell("python manage.py show users.AllUsers")
... 
========== =========== ============ ===========
 Username   User type   First name   Last name
---------- ----------- ------------ -----------
 jdupond                Jean         Dupond
 pbommel                Piet         Bommel
========== =========== ============ ===========

The Instantiator class

Since .py fixtures are normal Python modules, there are no more limits to our phantasy when creating new objects. A first thing that might drop into our mind is that there should be a more "compact" way to create many records of a same table.

A quick generic method for writing more compact fixtures this is the Instantiator class. Here is the same fixture using an instantiator:

1from lino.utils.instantiator import Instantiator
2
3User = Instantiator('users.User', 'username first_name last_name email').build
4
5
6def objects():
7    yield User('pbommel', 'Piet', 'Bommel', 'piet@bommel.be')
8    yield User('jdupond', 'Jean', 'Dupond', 'jean@bommel.be')

Note that the name User in that file refers to the build method of an Instantiator instance, not to some User model.

The Instantiator class is just a little utility. It helps us to eliminate some lines of the code, nothing more (and nothing less). Compare the two source files on this page and imagine you want to maintain these fixtures. For example add a third user, or add a new field for every user. Which one will be easier to maintain?

Python fixtures are intelligent

Note the difference between "intelligent" and "dumped" fixtures: An intelligent fixture is written by a human and used to provide demo data to a Lino application (see Writing Python fixtures). A dumped fixture is generated by the dumpdata or dump2py command and looks much less readable because it is optimized to allow automated database migrations.

Python fixtures are a powerful tool. You can use them to generate demo data in many different ways. Look for example at the source code of the following fixtures:

Play with them by trying your own combinations:

$ python manage.py initdb std all_countries be few_languages props demo
$ python manage.py initdb std few_languages few_countries few_cities demo
...

Exercise

  • Get inspired by the examples above and extend your dumpy2.py fixture.

  • Publish your code somewhere (e.g. in a blog or on GitHub) so that we can refer to it here and others can learn from it.

Python fixtures are modularizable

Lino encourages fine-grained modularity of your fixtures because as an application developer your can use the demo_fixtures setting in order to specify a default set of fixture names to be loaded. Check the Introduction to demo fixtures section in case you didn't know this.

Python fixtures don't like relative imports

There is one (minor) limitation to your fantasy when writing Python fixtures: you cannot use relative imports in a Python fixture. See here

Conclusion

Python fixtures are an important tool for application developers because

  • they are more flexible than json or xml fixtures and easy to adapt when your database structure changes.

  • they provide a simple and modular way to deploy demo data for your application