Login

Dump a model instance and related objects as a Python data structure

Author:
akaihola
Posted:
January 31, 2012
Language:
Python
Version:
1.3
Score:
0 (after 0 ratings)

This utility makes a text dump of a model instance, including objects related by a forward or reverse foreign key. The result is a hierarchical data structure where

  • each instance is represented as a list of fields,

  • each field as a (<name>, <value>) tuple,

  • each <value> as a primitive type, a related object (as a list of fields), or a list of related objects.

See the docstring for examples.

We used this to make text dumps of parts of the database before and after running a batch job. The format was more useful than stock dumpdata output since all related data is included with each object. These dumps lend themselves particularly well for comparison with a visual diff tool like Meld.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
from django.db import models


def shift_paths(exclude, name):
    return tuple(item.split('.', 1)[1] for item in exclude
                 if item.startswith(('{0}.'.format(name), '*.')))


def deep_dump_instance(instance,
                       depth=1,
                       exclude=(),
                       include=(),
                       order_by=(),
                       seen=None):
    """Deep-dumps fields of a model instance as (name, value) tuples

    Examples::

        # create a fixture
        >>> my_poll = Poll.objects.create(question=u"What's up?",
                                          pub_date=datetime.datetime.now())
        >>> choice_1 = my_poll.choice_set.create(choice='Not much', votes=5)
        >>> choice_2.choice_set.create(choice='The sky', votes=2)

        # recurse all related objects
        >>> deep_dump_instance(my_poll)
        [('question', u"What's up?"),
         ('pub_date', datetime.datetime(2012, 1, 30, 9, 48)),
         ('choice_set',
          [[('choice', u'Not much'), ('votes', 5)],
           [('choice', u'The sky'), ('votes', 2)]])]

        # skip all related objects
        >>> deep_dump_instance(my_poll, depth=0)
        [('question', u"What's up?"),
         ('pub_date', datetime.datetime(2012, 1, 30, 9, 48))]

        # exclude a field
        >>> deep_dump_instance(my_poll, exclude=['pub_date'])
        [('question', u"What's up?"),
         ('choice_set',
          [[('choice', u'Not much'), ('votes', 5)],
           [('choice', u'The sky'), ('votes', 2)]])]

        # only include a field in related objects
        >>> deep_dump_instance(choice_1,
        ...                    exclude=['*', 'question.*'],
        ...                    include=['poll', 'poll.pub_date'])
        [[('poll',
          [('pub_date', datetime.datetime(2012, 1, 30, 9, 48))])]]

        # sort related objects
        >>> deep_dump_instance(my_poll,
        ...                    exclude=['*'],
        ...                    include=['choice_set'],
        ...                    order_by=['choice_set.votes'])
        [('choice_set',
          [[('choice', u'The sky'), ('votes', 2)],
           [('choice', u'Not much'), ('votes', 5)]])]

    """
    if not seen:
        seen = set()
    if (instance.__class__, instance.pk) in seen:
        return '<recursive>'
    seen.add((instance.__class__, instance.pk))
    field_names = sorted(
        [field.name for field in instance._meta.fields] +
        [f.get_accessor_name() for f in instance._meta.get_all_related_objects()])
    
    dump = []
    exclude_all = '*' in exclude
    for name in field_names:
        if name in include or (not exclude_all and name not in exclude):
            try:
                value = getattr(instance, name)
            except models.ObjectDoesNotExist:
                value = None
            if value.__class__.__name__ == 'RelatedManager':
                if depth >= 1:
                    related_objects = value.all()
                    for ordering in order_by:
                        parts = ordering.split('.')
                        if len(parts) == 2 and parts[0] == name:
                            related_objects = related_objects.order_by(parts[1])
                    value = [deep_dump_instance(related,
                                                depth=depth-1,
                                                exclude=shift_paths(exclude, name),
                                                include=shift_paths(include, name),
                                                order_by=shift_paths(order_by, name),
                                                seen=seen)
                             for related in related_objects]
                else:
                    continue
            elif isinstance(value, models.Model):
                if depth >= 1:
                    value = deep_dump_instance(value,
                                               depth=depth-1,
                                               exclude=shift_paths(exclude, name),
                                               include=shift_paths(include, name),
                                               order_by=shift_paths(order_by, name),
                                               seen=seen)
                else:
                    continue
            dump.append((name, value))
    return dump

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 2 weeks ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 10 months, 1 week ago
  5. Help text hyperlinks by sa2812 11 months ago

Comments

ckarrie2 (on January 31, 2012):

Interesting. Is there a way to "load" the data back?

#

Please login first before commenting.