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.