Django model initial data set up
On many occasions it can be helpful or necessary to load a set of pre-defined data records on a Django model. Django allows you to load pre-defined records by either hard-coding them in Python, using a standard SQL script with SQL statements or using a fixture file which is a Django export/import format described in the previous section.
The first step to load a set of pre-defined data records is to generate an empty migration file to handle the actual data loading process. Listing 7-31 illustrates how to generate an empty migration file.
Listing 7-31 Create empty Django migration file to load initial data for Django model
[user@coffeehouse ~]$ python manage.py makemigrations --empty stores Migrations for 'stores': 0002_auto_20180124_0507.py:
As you can see in listing 7-31,
Django creates the empty migration file
0002_auto_20180124_0507.py
for the stores app. At this
point, you can easily rename the migration file as described in the
previous section. Once you have an empty Django migration, let's
explore the different ways to modify it to set up initial data for
a Django model.
Hard-code predefined records in Python migration file
The simplest approach to set up initial data is to hard-code the set of pre-defined data records and make it part of the migration file itself. Listing 7-32 illustrates a modified migration file with hard-coded model objects to load into a database.
Listing 7-32 Load initial data with hard-coded data in Django migration file
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations def load_stores(apps, schema_editor): Store = apps.get_model("stores", "Store") store_corporate = Store(id=0,name='Corporate',address='624 Broadway', city='San Diego',state='CA',email='corporate@coffeehouse.com') store_corporate.save() store_downtown = Store(id=1,name='Downtown',address='Horton Plaza', city='San Diego',state='CA',email='downtown@coffeehouse.com') store_downtown.save() store_uptown = Store(id=2,name='Uptown',address='1240 University Ave', city='San Diego',state='CA',email='uptown@coffeehouse.com') store_uptown.save() store_midtown = Store(id=3,name='Midtown',address='784 W Washington St', city='San Diego',state='CA',email='midtown@coffeehouse.com') store_midtown.save() def delete_stores(apps, schema_editor): Store = apps.get_model("stores", "Store") Store.objects.all().delete() class Migration(migrations.Migration): dependencies = [ ('stores', '0001_initial'), ] operations = [ migrations.RunPython(load_stores,delete_stores), ]
The first thing that's added to
the empty migration file is the
migrations.RunPython(load_stores,delete_stored)
line
in the operations[]
list. The RunPython
method runs Python code and it's first argument indicates to run
the load_stores
method, the second argument -- which
is optional -- is called the reverse code and is run when rolling
back migrations -- which is mention in the previous section on
'Migration file rollback'.
The load_stores
method in listing 7-32 contains the hard-coded data records. This
method first gets a reference of the Store
model and
then creates three different instances which are then saved to the
database. The delete_stores
method does the opposite
of the load_stores
method -- as it's purpose is to
rollback the applied data -- deleting Store
instances.
Once you make the additions
illustrated in listing 7-32 to an empty migration file, you just
need to trigger the migration with the migrate
command
to load data into the database.
SQL script with SQL statements
On other occasions you may
already have a set of predefined records in an SQL script to
populate a database table. Listing 7-33 illustrates a sample SQL
script to populate the table associated with the Store
model.
Listing 7-33. SQL script with SQL statements
INSERT INTO stores_store (id,name,address,city,state,email) VALUES (0,'Corporate','624 Broadway','San Diego','CA','corporate@coffeehouse.com'); INSERT INTO stores_store (id,name,address,city,state,email) VALUES (1,'Downtown','Horton Plaza','San Diego','CA','downtown@coffeehouse.com'); INSERT INTO stores_store (id,name,address,city,state,email) VALUES (2,'Uptown','1240 University Ave','San Diego','CA','uptown@coffeehouse.com'); INSERT INTO stores_store (id,name,address,city,state,email) VALUES (3,'Midtown','784 W Washington St','San Diego','CA','midtown@coffeehouse.com');
By convention, Django names SQL
scripts after the Django model it's storing data for and places the
SQL scripts in a sub-folder named sql
inside the app
where the models are. For example, the contents of listing 7-33 are
for the Store
model in the stores
app and
therefore would be placed in the project folder
stores/sql/store.sql
. Once you have an SQL script
inside a Django project's directory structure, you can set it up as
the initial data for a Django model. Listing 7-34 illustrates a
modified migration file to load data from the SQL script in listing
7-33 into a database.
Listing 7-34. Load initial data with SQL script in Django migration file
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations def load_stores_from_sql(): from coffeehouse.settings import PROJECT_DIR import os sql_statements = open(os.path.join(PROJECT_DIR,'stores/sql/store.sql'), 'r').read() return sql_statements def delete_stores_with_sql(): return 'DELETE from stores_store;' class Migration(migrations.Migration): dependencies = [ ('stores', '0001_initial'), ] operations = [ migrations.RunSQL(load_stores_from_sql(), delete_stores_with_sql()), ]
Note The RunSQL method used to load SQL statements
relies on the sqlparse package. So if you plan to use this
functionality you need install this package (e.g. pip install sqlparse
).
The first thing that's added to
the empty migration file is the
migrations.RunSQL(load_stores_from_sql(),delete_stores_with_sql())
line in the operations[]
list. The RunSQL
method runs SQL statements, and it's first argument indicates to
run the load_stores
method, the second argument --
which is optional -- is called the reverse code and is run when
rolling back migrations -- described in the previous section on 'Migration file rollback'
The
load_stores_from_sql
method in listing 7-34 reads the
contents of the SQL script from a relative path in the main project
directory at stores/sql/store.sql
and returns the SQL
statements in the file. In this case, the relative path is provided
by the PROJECT_DIR
variable defined a Django project's
settings.py
file and the SQL script is read using
Python's standard open
method. The
delete_stores_from_sql
method does the opposite of the
load_stores_from_sql
method -- as it's purpose is to
rollback the applied data -- deleting Store
instances.
Once you make the additions
illustrated in listing 7-34 to an empty migration file, you just
need to trigger the migration with the migrate
command
to load data into the database.
Django fixture file
Another alternative to load
initial data in a Django model is through a fixture file. A fixture
file is a Django specific format used to manage the data
export/import of Django models, described in the previous section
on Django model database tasks. Listing 7-35 illustrates a JSON
fixture file to populate the table associated with the
Store
model.
Listing 7-35. Django fixture file with JSON structure
[{ "fields": { "city": "San Diego", "state": "CA", "email": "corporate@coffeehouse.com", "name": "Corporate", "address": "624 Broadway" }, "model": "stores.store", "pk": 0 }, { "fields": { "city": "San Diego", "state": "CA", "email": "downtown@coffeehouse.com", "name": "Downtown", "address": "Horton Plaza" }, "model": "stores.store", "pk": 1 }]
Tip Use dumpdata to generate fixture files, as described in the model database tasks section.
By convention, Django names
fixture files after the Django model its stores data for and places
fixture files in a sub-folder named fixtures
inside
the app where a model is located. For example, the contents of
listing 7-35 are for the Store
model in the
stores
app, therefore they're placed in the project
folder stores/fixtures/store.json
. Once you have an
fixture file inside a Django project's directory structure, you can
take the next step to set it up as the initial data for a Django
model.
Listing 7-36 illustrates a modified migration file to load data from the fixture file in listing 7-35 into a database.
Listing 7-36. Load initial data from Django fixture file in Django migration file
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations def load_stores_from_fixture(apps, schema_editor): from django.core.management import call_command call_command("loaddata", "store") def delete_stores(apps, schema_editor): Store = apps.get_model("stores", "Store") Store.objects.all().delete() class Migration(migrations.Migration): dependencies = [ ('stores', '0001_initial'), ] operations = [ migrations.RunPython(load_stores_from_fixture,delete_stores), ]
The first thing that's added to
the empty migration file is the
migrations.RunPython(load_stores_from_fixture,delete_stores)
line in the operations[]
list. The
RunPython
method runs Python code as previously
described in listing 7-32.
The
load_stores_from_fixture
method in listing 7-36 uses
the call_command
method to load the fixture file by
simulating the command line execution of the manage.py
loaddata
command. The loaddata
command requires
an additional argument to search for a fixture file in a specific
app. In this case, the argument store
tells Django to
look for a fixture files named store
in all the
fixtures
sub-directories for all apps. Note the
RunPython()
also uses a reverse code argument -- as it
was done in listing 7-32 -- to be able to rollback the loading of
fixture data.
Once you make the additions
illustrated in listing 7-36 to an empty migration file, you just
need to trigger the migration with the migrate
command
to load data into the database.