Friday, April 20, 2012

bitkickers: Django management command to list, filter and exclude ...

Dumping Django data to a fixture and loading it back up again are accomplished with the built-in management commands dumpdata and loaddata. However, loading a fixture into an existing database is a little trickier than loading it into an empty database. Because the fixture json contains the original primary keys of the records, you can get integrity errors.

Recently, I had a use case where I wanted to recover from this situation by excluding some models from the fixture. What I came up with is a new management command called copydata that takes an existing fixture file and can list, filter and exclude a subset of the models.

 from django.core.management.base import BaseCommand from optparse import make_option import json   class Command(BaseCommand):      operation = None     filters = []     excludes = []      args = '<file [--list|--filter|--exclude]>'     help = 'Copy/filter JSON fixture data to strip out certain models. ' \         'Useful if certain parts of a fixture are failing.'      option_list = BaseCommand.option_list + (             make_option('--list',                 action='store_true',                 dest='list',                 default=False,                 help='List the models in a fixture'),             make_option('--filter',                 action='store',                 dest='filter',                 default=False,                 help='Output the fixture with only these models, ' \                     'comma-separated list.'),             make_option('--exclude',                 action='store',                 dest='exclude',                 default=False,                 help='Output the fixture without these models, ' \                     'comma-separated list.'),             )      def handle(self, file_path=None, list=False, filter=None, exclude=None, **options):          if list:             self.operation = 'list'         if filter:             self.filters = filter.split(',')             self.operation = 'copy'         if exclude:             self.excludes = exclude.split(',')             self.operation = 'copy'          self.call('init')         for record in json.load(open(file_path)):             self.call('iter', record)         self.call('final')      def call(self, func_postfix, *args, **kwargs):         func_name = self.operation + '_' + func_postfix         if hasattr(self, func_name):             func = getattr(self, func_name)             func(*args, **kwargs)      def list_init(self):         self.model_set = set()      def list_iter(self, record):         self.model_set.add(record.get('model'))      def list_final(self):         for model in list(self.model_set):             print model      def copy_init(self):         self.json_out = []      def copy_iter(self, record):          if self.filters and record.get('model') not in self.filters:             return          if self.excludes and record.get('model') in self.excludes:             return          self.json_out.append(record)      def copy_final(self):         print json.dumps(self.json_out, indent=4) 

After saving this file as copydata.py inside the management/commands directory of your Django application, you can do the following.

 >./manage.py dumpdata myapp > /tmp/fixture.json  >./manage.py copydata /tmp/fixture.json --list myapp.friend myapp.invite myapp.profile  >./manage.py copydata /tmp/fixture.json --filter myapp.friend > /tmp/just_friends.json  >./manage.py copydata /tmp/fixture.json --exclude myapp.friend > /tmp/just_invites_and_profiles.json 

If you still have access to the database where the fixture was dumped from, it's more straight forward to just dump the data again and use the built-in appname.Model arguments on the command-line, though you will need to list ALL of them in the exclude case.

0 comments:

Post a Comment