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 Your first local Lino project tutorial.
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
.py and designed to being imported and exectued during
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.
$ cd ~/projects/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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
from lino.api import dd, rt def objects(): User = rt.models.users.User yield User( username='pbommel', first_name='Piet', last_name='Bommel', email@example.com') yield User( username='jdupond', first_name='Jean', last_name='Dupond', firstname.lastname@example.org')
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. Operations to perform: Synchronize unmigrated apps: about, bootstrap3, cal, checkdata, contacts, countries, export_excel, extjs, gfks, jinja, lino, office, printing, staticfiles, system, users, xl Apply all migrations: contenttypes, sessions Synchronizing apps without migrations: Creating tables... Creating table system_siteconfig Creating table users_user Creating table users_authority Creating table countries_country Creating table countries_place Creating table contacts_partner Creating table contacts_person Creating table contacts_companytype Creating table contacts_company Creating table contacts_roletype Creating table contacts_role Creating table gfks_helptext Creating table checkdata_problem Creating table cal_remotecalendar Creating table cal_room Creating table cal_eventtype Creating table cal_guestrole Creating table cal_calendar Creating table cal_subscription Creating table cal_task Creating table cal_eventpolicy Creating table cal_recurrentevent Creating table cal_event Creating table cal_guest Running deferred SQL... Running migrations: Applying contenttypes.0001_initial... OK Applying contenttypes.0002_remove_content_type_name... OK Applying sessions.0001_initial... OK Loading data from .../docs/dev/pyfixtures/fixtures/dumpy1.py Installed 2 object(s) from 1 fixture(s)
Let's use the
show command to see whether our data has been
$ 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 ========== =========== ============ ===========
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
Here is the same fixture using an instantiator:
1 2 3 4 5 6 7 8 9 10 11
from lino.utils.instantiator import Instantiator User = Instantiator( 'users.User', 'username first_name last_name email').build def objects(): yield User('pbommel', 'Piet', 'Bommel', 'email@example.com') yield User('jdupond', 'Jean', 'Dupond', 'firstname.lastname@example.org')
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?
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 ...
Get inspired by the examples above and extend your
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.
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
Demo fixtures section in case you didn't know this.
There is one (minor) limitation to your phantasy when writing Python fixtures: you cannot use relative imports in a Python fixture. See here