Fixtures : handling static data in the database

Some tables need to be filled with default data when users start to work with the application. The Camelot fixture module camelot.model.fixture assist in handling this kind of data.

Suppose we have an entity PartyCategory to divide Persons and Organizations into certain groups.

The complete definition of such an entity can be found in camelot.model.authentication.PartyCategory.

To make things easier for the first time user, some prefab categories should be available when the user starts the application. Such as Suspect, Prospect, VIP.

When to update fixtures

Most of the time static data should be created or updated right after the model has been set up and before the user starts using the application.

The easiest place to do this is in the setup_model method inside the settings.py module.

So we rewrite settings.py to include a call to a new update_fixtures method:

def update_fixtures():
  """Update static data in the database"""
  from camelot.model.fixture import Fixture
  from model import MovieType

def setup_model():
  from camelot.model import *
  from camelot.model.memento import *
  from camelot.model.synchronization import *
  from camelot.model.authentication import *
  from camelot.model.i18n import *
  from camelot.model.fixture import *
  from model import *
  setup_all(create_tables=True)
  updateLastLogin()
  update_fixtures()

Creating new data

When creating new data with the fixture module, a reference to the created data will be stored in the fixture table along with a ‘fixture key’. This fixture key can be used later to retrieve or update the created data.

So lets create some new movie types:

def update_fixtures():
  """Update static data in the database"""
  from camelot.model.fixture import Fixture
  from model import MovieType
  Fixture.insertOrUpdateFixture(MovieType,
                                fixture_key = 'comic',
                                values = dict(name='Comic'))
  Fixture.insertOrUpdateFixture(MovieType,
                                fixture_key = 'scifi',
                                values = dict(name='Science Fiction'))

Fixture keys should be unique for each Entity class.

Update fixtures

When a new version of the application gets released, we might want to change the static data and add some icons to the movie types. Thanks to the ‘fixture key’, it’s easy to retrieve and update the already inserted data, just modify the update_fixtures function:

def update_fixtures():
  """Update static data in the database"""
  from camelot.model.fixture import Fixture
  from model import MovieType
  Fixture.insertOrUpdateFixture(MovieType,
                                fixture_key = 'comic',
                                values = dict(name='Comic', icon='spiderman.png'))
  Fixture.insertOrUpdateFixture(MovieType,
                                fixture_key = 'scifi',
                                values = dict(name='Science Fiction', icon='light_saber.png'))

The fixture version

In case lots of data needs to be read into the database (like a list of postal codeds), it might make no sense to create a new fixture for each code, instead a fixture version number can be set to indicate a list has been read into the database. The camelot.model.fixture.FixtureVersion exists to facilitate this.

        import csv
        if FixtureVersion.get_current_version( u'demo_data' ) == 0:
            reader = csv.reader( open( example_file ) )
            for line in reader:
                Person( first_name = line[0], last_name = line[1] )
            FixtureVersion.set_current_version( u'demo_data', 1 )
            Person.query.session.flush()
        # end load csv if fixture version
        self.assertTrue( Person.query.count() > person_count_before_import )
        self.assertEqual( FixtureVersion.get_current_version( u'demo_data' ),
                          1 )
        
class StatusCase( TestMetaData ):
    
    def test_status_type( self ):
        Entity, session = self.Entity, self.session
        
        #begin status type definition
        from camelot.model import type_and_status
        
        class Invoice( Entity, type_and_status.StatusMixin ):
            book_date = schema.Column( types.Date(), nullable = False )
            status = type_and_status.Status()
            
        #end status type definition
        self.create_all()
        self.assertTrue( issubclass( Invoice._status_type, type_and_status.StatusType ) )
        self.assertTrue( issubclass( Invoice._status_history, type_and_status.StatusHistory ) )
        #begin status types definition
        draft = Invoice._status_type( code = 'DRAFT' )
        ready = Invoice._status_type( code = 'READY' )
        session.flush()
        #end status types definition
        self.assertTrue( unicode( ready ) )
        #begin status type use
        invoice = Invoice( book_date = datetime.date.today() )
        self.assertEqual( invoice.current_status, None )
        invoice.change_status( draft, status_from_date = datetime.date.today() )
        #end status type use
        self.assertEqual( invoice.current_status, draft )
        self.assertEqual( invoice.get_status_from_date( draft ), datetime.date.today() )
        self.assertTrue( len( invoice.status ) )
        for history in invoice.status:
            self.assertTrue( unicode( history ) )
        
    def test_status_enumeration( self ):
        Entity, session = self.Entity, self.session
        
        #begin status enumeration definition
        from camelot.model import type_and_status
        
        class Invoice( Entity, type_and_status.StatusMixin ):
            book_date = schema.Column( types.Date(), nullable = False )
            status = type_and_status.Status( enumeration = [ (1, 'DRAFT'),
                                                             (2, 'READY') ] )
            
            class Admin( EntityAdmin ):
                list_display = ['book_date', 'current_status']
                list_actions = [ type_and_status.ChangeStatus( 'DRAFT' ),
                                 type_and_status.ChangeStatus( 'READY' ) ]
                form_actions = list_actions
                
        #end status enumeration definition
        self.create_all()
        self.assertTrue( issubclass( Invoice._status_history, type_and_status.StatusHistory ) )
        #begin status enumeration use
        invoice = Invoice( book_date = datetime.date.today() )
        self.assertEqual( invoice.current_status, None )
        invoice.change_status( 'DRAFT', status_from_date = datetime.date.today() )
        self.assertEqual( invoice.current_status, 'DRAFT' )
        self.assertEqual( invoice.get_status_from_date( 'DRAFT' ), datetime.date.today() )
        draft_invoices = Invoice.query.filter( Invoice.current_status == 'DRAFT' ).count()
        ready_invoices = Invoice.query.filter( Invoice.current_status == 'READY' ).count()        
        #end status enumeration use
        self.assertEqual( draft_invoices, 1 )
        self.assertEqual( ready_invoices, 0 )
        ready_action = Invoice.Admin.list_actions[-1]
        model_context = MockModelContext()
        model_context.obj = invoice
        list( ready_action.model_run( model_context ) )
        self.assertTrue( invoice.current_status, 'READY' )