Ticket #7052: t7052-surrogate-key.3.diff

File t7052-surrogate-key.3.diff, 44.4 KB (added by Russell Keith-Magee, 15 years ago)

Third draft - slight tweak to the algorithm resolving dependencies.

  • django/contrib/auth/models.py

    diff -r f266dfb70b06 django/contrib/auth/models.py
    a b  
    4747class SiteProfileNotAvailable(Exception):
    4848    pass
    4949
     50class PermissionManager(models.Manager):
     51    def get_by_surrogate(self, codename, app_label, model):
     52        return self.get(
     53            codename=codename,
     54            content_type=ContentType.objects.get_by_surrogate(app_label, model)
     55        )
     56
    5057class Permission(models.Model):
    5158    """The permissions system provides a way to assign permissions to specific users and groups of users.
    5259
     
    6370    name = models.CharField(_('name'), max_length=50)
    6471    content_type = models.ForeignKey(ContentType)
    6572    codename = models.CharField(_('codename'), max_length=100)
     73    objects = PermissionManager()
    6674
    6775    class Meta:
    6876        verbose_name = _('permission')
     
    7684            unicode(self.content_type),
    7785            unicode(self.name))
    7886
     87    def surrogate_key(self):
     88        return (self.codename,) + self.content_type.surrogate_key()
     89    surrogate_key.dependencies = ['contenttypes.contenttype']
     90
    7991class Group(models.Model):
    8092    """Groups are a generic way of categorizing users to apply permissions, or some other label, to those users. A user can belong to any number of groups.
    8193
  • django/contrib/contenttypes/models.py

    diff -r f266dfb70b06 django/contrib/contenttypes/models.py
    a b  
    88    # This cache is shared by all the get_for_* methods.
    99    _cache = {}
    1010
     11    def get_by_surrogate(self, app_label, model):
     12        try:
     13            ct = self.__class__._cache[(app_label, model)]
     14        except KeyError:
     15            ct = self.get(app_label=app_label, model=model)
     16        return ct
     17
    1118    def get_for_model(self, model):
    1219        """
    1320        Returns the ContentType object for a given model, creating the
     
    93100        so code that calls this method should catch it.
    94101        """
    95102        return self.model_class()._default_manager.get(**kwargs)
     103
     104    def surrogate_key(self):
     105        return (self.app_label, self.model)
  • django/core/management/commands/dumpdata.py

    diff -r f266dfb70b06 django/core/management/commands/dumpdata.py
    a b  
    6767        except KeyError:
    6868            raise CommandError("Unknown serialization format: %s" % format)
    6969
     70        # Now collate the objects to be serialized.
    7071        objects = []
    71         for app, model_list in app_list.items():
    72             if model_list is None:
    73                 model_list = get_models(app)
    74 
    75             for model in model_list:
    76                 if not model._meta.proxy:
    77                     objects.extend(model._default_manager.all())
     72        for model in sort_dependencies(app_list.items()):
     73            if not model._meta.proxy:
     74                objects.extend(model._default_manager.all())
    7875
    7976        try:
    8077            return serializers.serialize(format, objects, indent=indent)
     
    8279            if show_traceback:
    8380                raise
    8481            raise CommandError("Unable to serialize database: %s" % e)
     82
     83def sort_dependencies(app_list):
     84    """Sort a list of app,modellist pairs into a single list of models.
     85
     86    The single list of models is sorted so that any model with a surrogate key
     87    is serialized before a normal model, and any model with a surrogate key
     88    dependency has it's dependencies serialized first.
     89    """
     90    from django.db.models import get_model, get_models
     91    # Process the list of models, and get the list of dependencies
     92    model_dependencies = []
     93    models = set()
     94    for app, model_list in app_list:
     95        if model_list is None:
     96            model_list = get_models(app)
     97
     98        for model in model_list:
     99            models.add(model)
     100            if hasattr(model, 'surrogate_key'):
     101                deps = getattr(model.surrogate_key, 'dependencies', None)
     102                if deps:
     103                    model_dependencies.append((model, [get_model(*d.split('.')) for d in deps]))
     104                else:
     105                    model_dependencies.append((model, []))
     106            else:
     107                model_dependencies.append((model, []))
     108
     109    model_dependencies.reverse()
     110    # Now sort the models to ensure that dependencies are met. This
     111    # is done by repeatedly iterating over the input list of models.
     112    # If all the dependencies of a given model are in the final list,
     113    # that model is promoted to the end of the final list. This process
     114    # continues until the input list is empty, or we do a full iteration
     115    # over the input models without promoting a model to the final list.
     116    # If we do a full iteration without a promotion, that means there are
     117    # circular dependencies in the list.
     118    model_list = []
     119    while model_dependencies:
     120        skipped = []
     121        changed = False
     122        while model_dependencies:
     123            model, deps = model_dependencies.pop()
     124            if all((d not in models or d in model_list) for d in deps):
     125                # If all of the models in the dependency list are either already
     126                # on the final model list, or not on the original serialization list,
     127                # then we've found another model with all it's dependencies satisfied.
     128                model_list.append(model)
     129                changed = True
     130            else:
     131                skipped.append((model, deps))
     132        if not changed:
     133            raise CommandError("Can't resolve dependencies for %s in serialized app list." %
     134                ', '.join('%s.%s' % (model._meta.app_label, model._meta.object_name)
     135                for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__))
     136            )
     137        model_dependencies = skipped
     138
     139    return model_list
  • django/core/serializers/python.py

    diff -r f266dfb70b06 django/core/serializers/python.py
    a b  
    4747    def handle_fk_field(self, obj, field):
    4848        related = getattr(obj, field.name)
    4949        if related is not None:
    50             if field.rel.field_name == related._meta.pk.name:
    51                 # Related to remote object via primary key
    52                 related = related._get_pk_val()
     50            if hasattr(related, 'surrogate_key'):
     51                related = related.surrogate_key()
    5352            else:
    54                 # Related to remote object via other field
    55                 related = getattr(related, field.rel.field_name)
    56         self._current[field.name] = smart_unicode(related, strings_only=True)
     53                if field.rel.field_name == related._meta.pk.name:
     54                    # Related to remote object via primary key
     55                    related = related._get_pk_val()
     56                else:
     57                    # Related to remote object via other field
     58                    related = smart_unicode(getattr(related, field.rel.field_name), strings_only=True)
     59        self._current[field.name] = related
    5760
    5861    def handle_m2m_field(self, obj, field):
    5962        if field.rel.through._meta.auto_created:
    60             self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
     63            if hasattr(field.rel.to, 'surrogate_key'):
     64                m2m_value = lambda value: value.surrogate_key()
     65            else:
     66                m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True)
     67            self._current[field.name] = [m2m_value(related)
    6168                               for related in getattr(obj, field.name).iterator()]
    6269
    6370    def getvalue(self):
     
    8693
    8794            # Handle M2M relations
    8895            if field.rel and isinstance(field.rel, models.ManyToManyRel):
    89                 m2m_convert = field.rel.to._meta.pk.to_python
    90                 m2m_data[field.name] = [m2m_convert(smart_unicode(pk)) for pk in field_value]
     96                if hasattr(field.rel.to._default_manager, 'get_by_surrogate'):
     97                    def m2m_convert(value):
     98                        if hasattr(value, '__iter__'):
     99                            obj = field.rel.to._default_manager.get_by_surrogate(*value)
     100                        else:
     101                            obj = field.rel.to._default_manager.get_by_surrogate(value)
     102                        return obj.pk
     103                else:
     104                    m2m_convert = lambda v: smart_unicode(field.rel.to._meta.pk.to_python(v))
     105                m2m_data[field.name] = [m2m_convert(pk) for pk in field_value]
    91106
    92107            # Handle FK fields
    93108            elif field.rel and isinstance(field.rel, models.ManyToOneRel):
    94109                if field_value is not None:
    95                     data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
     110                    if hasattr(field.rel.to._default_manager, 'get_by_surrogate'):
     111                        if hasattr(field_value, '__iter__'):
     112                            obj = field.rel.to._default_manager.get_by_surrogate(*field_value)
     113                        else:
     114                            obj = field.rel.to._default_manager.get_by_surrogate(field_value)
     115                        data[field.attname] = getattr(obj, field.rel.field_name)
     116                    else:
     117                        data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
    96118                else:
    97119                    data[field.attname] = None
    98120
  • django/core/serializers/xml_serializer.py

    diff -r f266dfb70b06 django/core/serializers/xml_serializer.py
    a b  
    8181        self._start_relational_field(field)
    8282        related = getattr(obj, field.name)
    8383        if related is not None:
    84             if field.rel.field_name == related._meta.pk.name:
    85                 # Related to remote object via primary key
    86                 related = related._get_pk_val()
     84            if hasattr(related, 'surrogate_key'):
     85                # If related object has a surrogate, use it
     86                related = related.surrogate_key()
     87                if hasattr(related, '__iter__'):
     88                    # Iterable surrogates are rolled out as subelements
     89                    for key_value in related:
     90                        self.xml.addQuickElement("surrogate", attrs={
     91                            'value' : smart_unicode(key_value)
     92                        })
     93                else:
     94                    # Otherwise just output the key value as a string
     95                    self.xml.characters(smart_unicode(related))
    8796            else:
    88                 # Related to remote object via other field
    89                 related = getattr(related, field.rel.field_name)
    90             self.xml.characters(smart_unicode(related))
     97                if field.rel.field_name == related._meta.pk.name:
     98                    # Related to remote object via primary key
     99                    related = related._get_pk_val()
     100                else:
     101                    # Related to remote object via other field
     102                    related = getattr(related, field.rel.field_name)
     103                self.xml.characters(smart_unicode(related))
    91104        else:
    92105            self.xml.addQuickElement("None")
    93106        self.xml.endElement("field")
     
    100113        """
    101114        if field.rel.through._meta.auto_created:
    102115            self._start_relational_field(field)
     116            if hasattr(field.rel.to, 'surrogate_key'):
     117                # If the objects in the m2m have a surrogate, use it
     118                def handle_m2m(value):
     119                    surrogate = value.surrogate_key()
     120                    if hasattr(surrogate, '__iter__'):
     121                        # Iterable surrogates are rolled out as subelements
     122                        self.xml.startElement("object", {})
     123                        for key_value in surrogate:
     124                            self.xml.addQuickElement("surrogate", attrs={
     125                                'value' : smart_unicode(key_value)
     126                            })
     127                        self.xml.endElement("object")
     128                    else:
     129                        # Otherwise just output the element
     130                        self.xml.addQuickElement("object", attrs={
     131                            'surrogate' : smart_unicode(surrogate)
     132                        })
     133            else:
     134                def handle_m2m(value):
     135                    self.xml.addQuickElement("object", attrs={
     136                        'pk' : smart_unicode(value._get_pk_val())
     137                    })
    103138            for relobj in getattr(obj, field.name).iterator():
    104                 self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
     139                handle_m2m(relobj)
     140
    105141            self.xml.endElement("field")
    106142
    107143    def _start_relational_field(self, field):
     
    187223        if node.getElementsByTagName('None'):
    188224            return None
    189225        else:
    190             return field.rel.to._meta.get_field(field.rel.field_name).to_python(
    191                        getInnerText(node).strip())
     226            if hasattr(field.rel.to._default_manager, 'get_by_surrogate'):
     227                keys = node.getElementsByTagName('surrogate')
     228                if keys:
     229                    # If there are 'key' subelements, it must be an iterable surrogate
     230                    field_value = [k.getAttribute('value') for k in keys]
     231                    obj = field.rel.to._default_manager.get_by_surrogate(*field_value)
     232                else:
     233                    # Otherwise, just use the field text as the surrogate
     234                    field_value = getInnerText(node).strip()
     235                    obj = field.rel.to._default_manager.get_by_surrogate(field_value)
     236                return getattr(obj, field.rel.field_name)
     237            else:
     238                field_value = getInnerText(node).strip()
     239                return field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
    192240
    193241    def _handle_m2m_field_node(self, node, field):
    194242        """
    195243        Handle a <field> node for a ManyToManyField.
    196244        """
    197         return [field.rel.to._meta.pk.to_python(
    198                     c.getAttribute("pk"))
    199                     for c in node.getElementsByTagName("object")]
     245        if hasattr(field.rel.to._default_manager, 'get_by_surrogate'):
     246            def m2m_convert(n):
     247                keys = n.getElementsByTagName('surrogate')
     248                if keys:
     249                    # If there are 'key' subelements, it must be an iterable surrogate
     250                    field_value = [k.getAttribute('value') for k in keys]
     251                    obj = field.rel.to._default_manager.get_by_surrogate(*field_value)
     252                else:
     253                    # Otherwise, just use the field attribute as the surrogate
     254                    field_value = n.getAttribute('surrogate')
     255                    obj = field.rel.to._default_manager.get_by_surrogate(field_value)
     256                return obj.pk
     257        else:
     258            m2m_convert = lambda n: field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
     259        return [m2m_convert(c) for c in node.getElementsByTagName("object")]
    200260
    201261    def _get_model_from_node(self, node, attr):
    202262        """
  • new file tests/modeltests/fixtures/fixtures/fixture6.json

    diff -r f266dfb70b06 tests/modeltests/fixtures/fixtures/fixture6.json
    - +  
     1[
     2    {
     3        "pk": "1",
     4        "model": "fixtures.tag",
     5        "fields": {
     6            "name": "copyright",
     7            "tagged_type": ["fixtures", "article"],
     8            "tagged_id": "3"
     9        }
     10    },
     11    {
     12        "pk": "2",
     13        "model": "fixtures.tag",
     14        "fields": {
     15            "name": "law",
     16            "tagged_type": ["fixtures", "article"],
     17            "tagged_id": "3"
     18        }
     19    },
     20    {
     21        "pk": "1",
     22        "model": "fixtures.person",
     23        "fields": {
     24            "name": "Django Reinhardt"
     25        }
     26    },
     27    {
     28        "pk": "2",
     29        "model": "fixtures.person",
     30        "fields": {
     31            "name": "Stephane Grappelli"
     32        }
     33    },
     34        {
     35        "pk": "3",
     36        "model": "fixtures.person",
     37        "fields": {
     38            "name": "Prince"
     39        }
     40    }
     41]
  • new file tests/modeltests/fixtures/fixtures/fixture7.xml

    diff -r f266dfb70b06 tests/modeltests/fixtures/fixtures/fixture7.xml
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2<django-objects version="1.0">
     3    <object pk="2" model="fixtures.tag">
     4        <field type="CharField" name="name">legal</field>
     5        <field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel">
     6            <surrogate value="fixtures"/>
     7            <surrogate value="article"/>
     8        </field>
     9        <field type="PositiveIntegerField" name="tagged_id">3</field>
     10    </object>
     11    <object pk="3" model="fixtures.tag">
     12        <field type="CharField" name="name">django</field>
     13        <field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel">
     14            <surrogate value="fixtures"/>
     15            <surrogate value="article"/>
     16        </field>
     17        <field type="PositiveIntegerField" name="tagged_id">4</field>
     18    </object>
     19    <object pk="4" model="fixtures.tag">
     20        <field type="CharField" name="name">world domination</field>
     21        <field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel">
     22            <surrogate value="fixtures"/>
     23            <surrogate value="article"/>
     24        </field>
     25        <field type="PositiveIntegerField" name="tagged_id">4</field>
     26    </object>
     27</django-objects>
  • new file tests/modeltests/fixtures/fixtures/fixture8.json

    diff -r f266dfb70b06 tests/modeltests/fixtures/fixtures/fixture8.json
    - +  
     1[
     2    {
     3        "pk": "1",
     4        "model": "fixtures.visa",
     5        "fields": {
     6            "person": "Django Reinhardt",
     7            "permissions": [
     8                ["add_user", "auth", "user"],
     9                ["change_user", "auth", "user"],
     10                ["delete_user", "auth", "user"]
     11            ]
     12        }
     13    },
     14    {
     15        "pk": "2",
     16        "model": "fixtures.visa",
     17        "fields": {
     18            "person": "Stephane Grappelli",
     19            "permissions": [
     20                ["add_user", "auth", "user"]
     21            ]
     22        }
     23    },
     24        {
     25        "pk": "3",
     26        "model": "fixtures.visa",
     27        "fields": {
     28            "person": "Prince",
     29            "permissions": []
     30        }
     31    }
     32]
  • new file tests/modeltests/fixtures/fixtures/fixture9.xml

    diff -r f266dfb70b06 tests/modeltests/fixtures/fixtures/fixture9.xml
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2<django-objects version="1.0">
     3    <object pk="2" model="fixtures.visa">
     4        <field type="CharField" name="person">Stephane Grappelli</field>
     5        <field to="auth.permission" name="permissions" rel="ManyToManyRel">
     6            <object>
     7                <surrogate value="add_user"/>
     8                <surrogate value="auth"/>
     9                <surrogate value="user"/>
     10            </object>
     11            <object>
     12                <surrogate value="delete_user"/>
     13                <surrogate value="auth"/>
     14                <surrogate value="user"/>
     15            </object>
     16        </field>
     17    </object>
     18    <object pk="3" model="fixtures.person">
     19        <field type="CharField" name="name">Artist formerly known as &quot;Prince&quot;</field>
     20    </object>
     21    <object pk="3" model="fixtures.visa">
     22        <field type="CharField" name="person">Artist formerly known as &quot;Prince&quot;</field>
     23        <field to="auth.permission" name="permissions" rel="ManyToManyRel">
     24            <object>
     25                <surrogate value="change_user"/>
     26                <surrogate value="auth"/>
     27                <surrogate value="user"/>
     28            </object>
     29        </field>
     30    </object>
     31    <object pk="1" model="fixtures.book">
     32        <field type="CharField" name="name">Music for all ages</field>
     33        <field to="fixtures.person" name="authors" rel="ManyToManyRel">
     34            <object surrogate="Django Reinhardt"/>
     35            <object surrogate="Artist formerly known as &quot;Prince&quot;"/>
     36        </field>
     37    </object>
     38</django-objects>
  • tests/modeltests/fixtures/models.py

    diff -r f266dfb70b06 tests/modeltests/fixtures/models.py
    a b  
    88``FIXTURE_DIRS`` setting.
    99"""
    1010
     11from django.contrib.auth.models import Permission
     12from django.contrib.contenttypes import generic
     13from django.contrib.contenttypes.models import ContentType
    1114from django.db import models
    1215from django.conf import settings
    1316
     
    3134    class Meta:
    3235        ordering = ('-pub_date', 'headline')
    3336
     37class Blog(models.Model):
     38    name = models.CharField(max_length=100)
     39    featured = models.ForeignKey(Article, related_name='fixtures_featured_set')
     40    articles = models.ManyToManyField(Article, blank=True,
     41                                      related_name='fixtures_articles_set')
     42
     43    def __unicode__(self):
     44        return self.name
     45
     46
     47class Tag(models.Model):
     48    name = models.CharField(max_length=100)
     49    tagged_type = models.ForeignKey(ContentType, related_name="fixtures_tag_set")
     50    tagged_id = models.PositiveIntegerField(default=0)
     51    tagged = generic.GenericForeignKey(ct_field='tagged_type',
     52                                       fk_field='tagged_id')
     53
     54    def __unicode__(self):
     55        return '<%s: %s> tagged "%s"' % (self.tagged.__class__.__name__,
     56                                         self.tagged, self.name)
     57
     58class PersonManager(models.Manager):
     59    def get_by_surrogate(self, name):
     60        return self.get(name=name)
     61
     62class Person(models.Model):
     63    objects = PersonManager()
     64    name = models.CharField(max_length=100)
     65    def __unicode__(self):
     66        return self.name
     67
     68    class Meta:
     69        ordering = ('name',)
     70
     71    def surrogate_key(self):
     72        return self.name
     73
     74class Visa(models.Model):
     75    person = models.ForeignKey(Person)
     76    permissions = models.ManyToManyField(Permission, blank=True)
     77
     78    def __unicode__(self):
     79        return '%s %s' % (self.person.name,
     80                          ', '.join(p.name for p in self.permissions.all()))
     81
     82class Book(models.Model):
     83    name = models.CharField(max_length=100)
     84    authors = models.ManyToManyField(Person)
     85
     86    def __unicode__(self):
     87        return '%s by %s' % (self.name,
     88                          ' and '.join(a.name for a in self.authors.all()))
     89
     90    class Meta:
     91        ordering = ('name',)
     92
    3493__test__ = {'API_TESTS': """
    3594>>> from django.core import management
    3695>>> from django.db.models import get_app
     
    90149>>> Article.objects.all()
    91150[<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>]
    92151
     152# Load fixture 6, JSON file with dynamic ContentType fields. Testing ManyToOne.
     153>>> management.call_command('loaddata', 'fixture6.json', verbosity=0)
     154>>> Tag.objects.all()
     155[<Tag: <Article: Copyright is fine the way it is> tagged "copyright">, <Tag: <Article: Copyright is fine the way it is> tagged "law">]
     156
     157# Load fixture 7, XML file with dynamic ContentType fields. Testing ManyToOne.
     158>>> management.call_command('loaddata', 'fixture7.xml', verbosity=0)
     159>>> Tag.objects.all()
     160[<Tag: <Article: Copyright is fine the way it is> tagged "copyright">, <Tag: <Article: Copyright is fine the way it is> tagged "legal">, <Tag: <Article: Django conquers world!> tagged "django">, <Tag: <Article: Django conquers world!> tagged "world domination">]
     161
     162# Load fixture 8, JSON file with dynamic Permission fields. Testing ManyToMany.
     163>>> management.call_command('loaddata', 'fixture8.json', verbosity=0)
     164>>> Visa.objects.all()
     165[<Visa: Django Reinhardt Can add user, Can change user, Can delete user>, <Visa: Stephane Grappelli Can add user>, <Visa: Prince >]
     166
     167# Load fixture 9, XML file with dynamic Permission fields. Testing ManyToMany.
     168>>> management.call_command('loaddata', 'fixture9.xml', verbosity=0)
     169>>> Visa.objects.all()
     170[<Visa: Django Reinhardt Can add user, Can change user, Can delete user>, <Visa: Stephane Grappelli Can add user, Can delete user>, <Visa: Artist formerly known as "Prince" Can change user>]
     171
     172>>> Book.objects.all()
     173[<Book: Music for all ages by Artist formerly known as "Prince" and Django Reinhardt>]
     174
    93175# Load a fixture that doesn't exist
    94176>>> management.call_command('loaddata', 'unknown.json', verbosity=0)
    95177
    96178# object list is unaffected
    97179>>> Article.objects.all()
    98180[<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>]
     181
     182# Dump the current contents of the database as a JSON fixture
     183>>> management.call_command('dumpdata', 'fixtures', format='json')
     184[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16 16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16 15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16 14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": "Django Reinhardt", "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": "Stephane Grappelli", "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": "Artist formerly known as \\"Prince\\"", "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": ["Artist formerly known as \\"Prince\\"", "Django Reinhardt"]}}]
     185
     186# Dump the current contents of the database as an XML fixture
     187>>> management.call_command('dumpdata', 'fixtures', format='xml')
     188<?xml version="1.0" encoding="utf-8"?>
     189<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="5" model="fixtures.article"><field type="CharField" name="headline">XML identified as leading cause of cancer</field><field type="DateTimeField" name="pub_date">2006-06-16 16:00:00</field></object><object pk="4" model="fixtures.article"><field type="CharField" name="headline">Django conquers world!</field><field type="DateTimeField" name="pub_date">2006-06-16 15:00:00</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Copyright is fine the way it is</field><field type="DateTimeField" name="pub_date">2006-06-16 14:00:00</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker on TV is great!</field><field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field></object><object pk="1" model="fixtures.article"><field type="CharField" name="headline">Python program becomes self aware</field><field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><surrogate value="fixtures"></surrogate><surrogate value="article"></surrogate></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">legal</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><surrogate value="fixtures"></surrogate><surrogate value="article"></surrogate></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="3" model="fixtures.tag"><field type="CharField" name="name">django</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><surrogate value="fixtures"></surrogate><surrogate value="article"></surrogate></field><field type="PositiveIntegerField" name="tagged_id">4</field></object><object pk="4" model="fixtures.tag"><field type="CharField" name="name">world domination</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><surrogate value="fixtures"></surrogate><surrogate value="article"></surrogate></field><field type="PositiveIntegerField" name="tagged_id">4</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Artist formerly known as "Prince"</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object><object pk="1" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel">Django Reinhardt</field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><surrogate value="add_user"></surrogate><surrogate value="auth"></surrogate><surrogate value="user"></surrogate></object><object><surrogate value="change_user"></surrogate><surrogate value="auth"></surrogate><surrogate value="user"></surrogate></object><object><surrogate value="delete_user"></surrogate><surrogate value="auth"></surrogate><surrogate value="user"></surrogate></object></field></object><object pk="2" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel">Stephane Grappelli</field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><surrogate value="add_user"></surrogate><surrogate value="auth"></surrogate><surrogate value="user"></surrogate></object><object><surrogate value="delete_user"></surrogate><surrogate value="auth"></surrogate><surrogate value="user"></surrogate></object></field></object><object pk="3" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel">Artist formerly known as "Prince"</field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><surrogate value="change_user"></surrogate><surrogate value="auth"></surrogate><surrogate value="user"></surrogate></object></field></object><object pk="1" model="fixtures.book"><field type="CharField" name="name">Music for all ages</field><field to="fixtures.person" name="authors" rel="ManyToManyRel"><object surrogate='Artist formerly known as "Prince"'></object><object surrogate="Django Reinhardt"></object></field></object></django-objects>
     190
    99191"""}
    100192
    101193# Database flushing does not work on MySQL with the default storage engine
     
    159251>>> management.call_command('loaddata', 'fixture5', verbosity=0) # doctest: +ELLIPSIS
    160252Multiple fixtures named 'fixture5' in '...fixtures'. Aborting.
    161253
     254>>> management.call_command('flush', verbosity=0, interactive=False)
     255
     256# Load back in fixture 1, we need the articles from it
     257>>> management.call_command('loaddata', 'fixture1', verbosity=0)
     258
     259# Try to load fixture 6 using format discovery
     260>>> management.call_command('loaddata', 'fixture6', verbosity=0)
     261>>> Tag.objects.all()
     262[<Tag: <Article: Time to reform copyright> tagged "copyright">, <Tag: <Article: Time to reform copyright> tagged "law">]
     263
     264# Dump the current contents of the database as a JSON fixture
     265>>> management.call_command('dumpdata', 'fixtures', format='json')
     266[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}]
     267
     268# Dump the current contents of the database as an XML fixture
     269>>> management.call_command('dumpdata', 'fixtures', format='xml')
     270<?xml version="1.0" encoding="utf-8"?>
     271<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Time to reform copyright</field><field type="DateTimeField" name="pub_date">2006-06-16 13:00:00</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker has no place on ESPN</field><field type="DateTimeField" name="pub_date">2006-06-16 12:00:00</field></object><object pk="1" model="fixtures.article"><field type="CharField" name="headline">Python program becomes self aware</field><field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><surrogate value="fixtures"></surrogate><surrogate value="article"></surrogate></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">law</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><surrogate value="fixtures"></surrogate><surrogate value="article"></surrogate></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Prince</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object></django-objects>
     272
    162273"""
    163274
    164275from django.test import TestCase
  • new file tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json

    diff -r f266dfb70b06 tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json
    - +  
     1[
     2    {
     3        "pk": "4",
     4        "model": "fixtures_regress.person",
     5        "fields": {
     6            "name": "Neal Stephenson"
     7        }
     8    },
     9    {
     10        "pk": "2",
     11        "model": "fixtures_regress.store",
     12        "fields": {
     13            "name": "Amazon"
     14        }
     15    },
     16    {
     17        "pk": "3",
     18        "model": "fixtures_regress.store",
     19        "fields": {
     20            "name": "Borders"
     21        }
     22    },
     23    {
     24        "pk": 1,
     25        "model": "fixtures_regress.book",
     26        "fields": {
     27            "name": "Cryptonomicon",
     28            "author": "Neal Stephenson",
     29            "stores": ["Amazon", "Borders"]
     30        }
     31    }
     32]
     33 No newline at end of file
  • tests/regressiontests/fixtures_regress/models.py

    diff -r f266dfb70b06 tests/regressiontests/fixtures_regress/models.py
    a b  
    1313    specimens = models.Manager()
    1414
    1515    def __unicode__(self):
    16         return self.common_name
     16        return self.name
    1717
    1818def animal_pre_save_check(signal, sender, instance, **kwargs):
    1919    "A signal that is used to check the type of data loaded from fixtures"
     
    6969class Widget(models.Model):
    7070    name = models.CharField(max_length=255)
    7171
     72    class Meta:
     73        ordering = ('name',)
     74
     75    def __unicode__(self):
     76        return self.name
     77
    7278class WidgetProxy(Widget):
    7379    class Meta:
    7480        proxy = True
    7581
     82# Check for forward references in FKs and M2Ms with surrogate keys
     83
     84class TestManager(models.Manager):
     85    def get_by_surrogate(self, key):
     86        return self.get(name=key)
     87
     88class Store(models.Model):
     89    objects = TestManager()
     90    name = models.CharField(max_length=255)
     91
     92    class Meta:
     93        ordering = ('name',)
     94
     95    def __unicode__(self):
     96        return self.name
     97
     98    def surrogate_key(self):
     99        return self.name
     100
     101class Person(models.Model):
     102    objects = TestManager()
     103    name = models.CharField(max_length=255)
     104
     105    class Meta:
     106        ordering = ('name',)
     107
     108    def __unicode__(self):
     109        return self.name
     110
     111    # Person doesn't actually have a dependency on store, but we need to define
     112    # one to test the behaviour of the dependency resolution algorithm.
     113    def surrogate_key(self):
     114        return self.name
     115    surrogate_key.dependencies = ['fixtures_regress.store']
     116
     117class Book(models.Model):
     118    name = models.CharField(max_length=255)
     119    author = models.ForeignKey(Person)
     120    stores = models.ManyToManyField(Store)
     121
     122    class Meta:
     123        ordering = ('name',)
     124
     125
    76126__test__ = {'API_TESTS':"""
    77127>>> from django.core import management
    78128
     
    192242>>> management.call_command('dumpdata', 'fixtures_regress', format='json')
    193243[{"pk": 1, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]
    194244
     245###############################################
     246# Check that surrogate requirements are taken into account
     247# when serializing models
     248>>> management.call_command('loaddata', 'forward_ref_lookup.json', verbosity=0)
     249
     250>>> management.call_command('dumpdata', 'fixtures_regress.book', 'fixtures_regress.person', 'fixtures_regress.store', verbosity=0)
     251[{"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": ["Amazon", "Borders"], "name": "Cryptonomicon", "author": "Neal Stephenson"}}, {"pk": 2, "model": "fixtures_regress.store", "fields": {"name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}]
     252
     253# Now lets check the dependency sorting explicitly
     254
     255# First Some models with pathological circular dependencies
     256>>> class Circle1(models.Model):
     257...     name = models.CharField(max_length=255)
     258...     def surrogate_key(self):
     259...         return self.name
     260...     surrogate_key.dependencies = ['fixtures_regress.circle2']
     261
     262>>> class Circle2(models.Model):
     263...     name = models.CharField(max_length=255)
     264...     def surrogate_key(self):
     265...         return self.name
     266...     surrogate_key.dependencies = ['fixtures_regress.circle1']
     267
     268>>> class Circle3(models.Model):
     269...     name = models.CharField(max_length=255)
     270...     def surrogate_key(self):
     271...         return self.name
     272...     surrogate_key.dependencies = ['fixtures_regress.circle3']
     273
     274>>> class Circle4(models.Model):
     275...     name = models.CharField(max_length=255)
     276...     def surrogate_key(self):
     277...         return self.name
     278...     surrogate_key.dependencies = ['fixtures_regress.circle5']
     279
     280>>> class Circle5(models.Model):
     281...     name = models.CharField(max_length=255)
     282...     def surrogate_key(self):
     283...         return self.name
     284...     surrogate_key.dependencies = ['fixtures_regress.circle6']
     285
     286>>> class Circle6(models.Model):
     287...     name = models.CharField(max_length=255)
     288...     def surrogate_key(self):
     289...         return self.name
     290...     surrogate_key.dependencies = ['fixtures_regress.circle4']
     291
     292>>> class ExternalDependency(models.Model):
     293...     name = models.CharField(max_length=255)
     294...     def surrogate_key(self):
     295...         return self.name
     296...     surrogate_key.dependencies = ['fixtures_regress.book']
     297
     298# It doesn't matter what order you mention the models
     299# Store *must* be serialized before then Person.
     300>>> from django.core.management.commands.dumpdata import sort_dependencies
     301>>> sort_dependencies([('fixtures_regress', [Book, Person, Store])])
     302[<class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>]
     303
     304>>> sort_dependencies([('fixtures_regress', [Book, Store, Person])])
     305[<class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>]
     306
     307>>> sort_dependencies([('fixtures_regress', [Store, Book, Person])])
     308[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.Person'>]
     309
     310>>> sort_dependencies([('fixtures_regress', [Store, Person, Book])])
     311[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     312
     313>>> sort_dependencies([('fixtures_regress', [Person, Book, Store])])
     314[<class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>]
     315
     316>>> sort_dependencies([('fixtures_regress', [Person, Store, Book])])
     317[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.Person'>]
     318
     319# A dangling dependency - assume the user knows what they are doing.
     320>>> sort_dependencies([('fixtures_regress', [Person, Circle1, Store, Book])])
     321[<class 'regressiontests.fixtures_regress.models.Circle1'>, <class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.Person'>]
     322
     323# A tight circular dependency
     324>>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Store, Book])])
     325Traceback (most recent call last):
     326...
     327CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.
     328
     329>>> sort_dependencies([('fixtures_regress', [Circle1, Book, Circle2])])
     330Traceback (most recent call last):
     331...
     332CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.
     333
     334# A self referential dependency
     335>>> sort_dependencies([('fixtures_regress', [Book, Circle3])])
     336Traceback (most recent call last):
     337...
     338CommandError: Can't resolve dependencies for fixtures_regress.Circle3 in serialized app list.
     339
     340# A long circular dependency
     341>>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Circle3, Store, Book])])
     342Traceback (most recent call last):
     343...
     344CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2, fixtures_regress.Circle3 in serialized app list.
     345
     346# A dependency on a normal, non-surrogate model
     347>>> sort_dependencies([('fixtures_regress', [Person, ExternalDependency, Book])])
     348[<class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.ExternalDependency'>]
     349
    195350"""}
     351
Back to Top