Ticket #7052: t7052-rc1.diff

File t7052-rc1.diff, 55.9 KB (added by Russell Keith-Magee, 14 years ago)

RC1 of natural key lookup patch

  • django/contrib/auth/models.py

    diff -r e0fb67da6521 django/contrib/auth/models.py
    a b  
    4747class SiteProfileNotAvailable(Exception):
    4848    pass
    4949
     50class PermissionManager(models.Manager):
     51    def get_by_natural_key(self, codename, app_label, model):
     52        return self.get(
     53            codename=codename,
     54            content_type=ContentType.objects.get_by_natural_key(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 natural_key(self):
     88        return (self.codename,) + self.content_type.natural_key()
     89    natural_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 e0fb67da6521 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_natural_key(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 natural_key(self):
     105        return (self.app_label, self.model)
  • django/core/management/commands/dumpdata.py

    diff -r e0fb67da6521 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 natural key
     87    is serialized before a normal model, and any model with a natural 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            # Add any explicitly defined dependencies
     101            if hasattr(model, 'natural_key'):
     102                deps = getattr(model.natural_key, 'dependencies', [])
     103                if deps:
     104                    deps = [get_model(*d.split('.')) for d in deps]
     105            else:
     106                deps = []
     107
     108            # Now add a dependency for any FK or M2M relation with
     109            # a model that defines a natural key
     110            for field in model._meta.fields:
     111                if hasattr(field.rel, 'to'):
     112                    rel_model = field.rel.to
     113                    if hasattr(rel_model, 'natural_key'):
     114                        deps.append(rel_model)
     115            for field in model._meta.many_to_many:
     116                rel_model = field.rel.to
     117                if hasattr(rel_model, 'natural_key'):
     118                    deps.append(rel_model)
     119            model_dependencies.append((model, deps))
     120
     121    model_dependencies.reverse()
     122    # Now sort the models to ensure that dependencies are met. This
     123    # is done by repeatedly iterating over the input list of models.
     124    # If all the dependencies of a given model are in the final list,
     125    # that model is promoted to the end of the final list. This process
     126    # continues until the input list is empty, or we do a full iteration
     127    # over the input models without promoting a model to the final list.
     128    # If we do a full iteration without a promotion, that means there are
     129    # circular dependencies in the list.
     130    model_list = []
     131    while model_dependencies:
     132        skipped = []
     133        changed = False
     134        while model_dependencies:
     135            model, deps = model_dependencies.pop()
     136            if all((d not in models or d in model_list) for d in deps):
     137                # If all of the models in the dependency list are either already
     138                # on the final model list, or not on the original serialization list,
     139                # then we've found another model with all it's dependencies satisfied.
     140                model_list.append(model)
     141                changed = True
     142            else:
     143                skipped.append((model, deps))
     144        if not changed:
     145            raise CommandError("Can't resolve dependencies for %s in serialized app list." %
     146                ', '.join('%s.%s' % (model._meta.app_label, model._meta.object_name)
     147                for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__))
     148            )
     149        model_dependencies = skipped
     150
     151    return model_list
  • django/core/serializers/python.py

    diff -r e0fb67da6521 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, 'natural_key'):
     51                related = related.natural_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, 'natural_key'):
     64                m2m_value = lambda value: value.natural_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_natural_key'):
     97                    def m2m_convert(value):
     98                        if hasattr(value, '__iter__'):
     99                            return field.rel.to._default_manager.get_by_natural_key(*value).pk
     100                        else:
     101                            return smart_unicode(field.rel.to._meta.pk.to_python(value))
     102                else:
     103                    m2m_convert = lambda v: smart_unicode(field.rel.to._meta.pk.to_python(v))
     104                m2m_data[field.name] = [m2m_convert(pk) for pk in field_value]
    91105
    92106            # Handle FK fields
    93107            elif field.rel and isinstance(field.rel, models.ManyToOneRel):
    94108                if field_value is not None:
    95                     data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
     109                    if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
     110                        if hasattr(field_value, '__iter__'):
     111                            obj = field.rel.to._default_manager.get_by_natural_key(*field_value)
     112                            value = getattr(obj, field.rel.field_name)
     113                        else:
     114                            value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
     115                        data[field.attname] = value
     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 e0fb67da6521 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, 'natural_key'):
     85                # If related object has a natural key, use it
     86                related = related.natural_key()
     87                # Iterable natural keys are rolled out as subelements
     88                for key_value in related:
     89                    self.xml.startElement("natural", {})
     90                    self.xml.characters(smart_unicode(key_value))
     91                    self.xml.endElement("natural")
    8792            else:
    88                 # Related to remote object via other field
    89                 related = getattr(related, field.rel.field_name)
    90             self.xml.characters(smart_unicode(related))
     93                if field.rel.field_name == related._meta.pk.name:
     94                    # Related to remote object via primary key
     95                    related = related._get_pk_val()
     96                else:
     97                    # Related to remote object via other field
     98                    related = getattr(related, field.rel.field_name)
     99                self.xml.characters(smart_unicode(related))
    91100        else:
    92101            self.xml.addQuickElement("None")
    93102        self.xml.endElement("field")
     
    100109        """
    101110        if field.rel.through._meta.auto_created:
    102111            self._start_relational_field(field)
     112            if hasattr(field.rel.to, 'natural_key'):
     113                # If the objects in the m2m have a natural key, use it
     114                def handle_m2m(value):
     115                    natural = value.natural_key()
     116                    # Iterable natural keys are rolled out as subelements
     117                    self.xml.startElement("object", {})
     118                    for key_value in natural:
     119                        self.xml.startElement("natural", {})
     120                        self.xml.characters(smart_unicode(key_value))
     121                        self.xml.endElement("natural")
     122                    self.xml.endElement("object")
     123            else:
     124                def handle_m2m(value):
     125                    self.xml.addQuickElement("object", attrs={
     126                        'pk' : smart_unicode(value._get_pk_val())
     127                    })
    103128            for relobj in getattr(obj, field.name).iterator():
    104                 self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
     129                handle_m2m(relobj)
     130
    105131            self.xml.endElement("field")
    106132
    107133    def _start_relational_field(self, field):
     
    187213        if node.getElementsByTagName('None'):
    188214            return None
    189215        else:
    190             return field.rel.to._meta.get_field(field.rel.field_name).to_python(
    191                        getInnerText(node).strip())
     216            if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
     217                keys = node.getElementsByTagName('natural')
     218                if keys:
     219                    # If there are 'key' subelements, it must be an iterable natural key
     220                    field_value = [getInnerText(k).strip() for k in keys]
     221                    obj = field.rel.to._default_manager.get_by_natural_key(*field_value)
     222                    obj_pk = getattr(obj, field.rel.field_name)
     223                else:
     224                    # Otherwise, treat like a normal PK
     225                    field_value = getInnerText(node).strip()
     226                    obj_pk = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
     227                return obj_pk
     228            else:
     229                field_value = getInnerText(node).strip()
     230                return field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
    192231
    193232    def _handle_m2m_field_node(self, node, field):
    194233        """
    195234        Handle a <field> node for a ManyToManyField.
    196235        """
    197         return [field.rel.to._meta.pk.to_python(
    198                     c.getAttribute("pk"))
    199                     for c in node.getElementsByTagName("object")]
     236        if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
     237            def m2m_convert(n):
     238                keys = n.getElementsByTagName('natural')
     239                if keys:
     240                    # If there are 'natural' subelements, it must be a natural key
     241                    field_value = [getInnerText(k).strip() for k in keys]
     242                    obj_pk = field.rel.to._default_manager.get_by_natural_key(*field_value).pk
     243                else:
     244                    # Otherwise, treat like a normal PK value.
     245                    obj_pk = field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
     246                return obj_pk
     247        else:
     248            m2m_convert = lambda n: field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
     249        return [m2m_convert(c) for c in node.getElementsByTagName("object")]
    200250
    201251    def _get_model_from_node(self, node, attr):
    202252        """
  • docs/releases/1.2.txt

    diff -r e0fb67da6521 docs/releases/1.2.txt
    a b  
    130130:meth:`~django.core.mail.get_connection()` call::
    131131
    132132    connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234)
    133    
     133
    134134User Messages API
    135135-----------------
    136136
    137 The API for storing messages in the user ``Message`` model (via 
     137The API for storing messages in the user ``Message`` model (via
    138138``user.message_set.create``) is now deprecated and will be removed in Django
    1391391.4 according to the standard :ref:`release process <internals-release-process>`.
    140140
     
    147147    from django.contrib import messages
    148148    messages.add_message(request, messages.INFO, 'a message')
    149149
    150 Additionally, if you make use of the method, you need to replace the 
     150Additionally, if you make use of the method, you need to replace the
    151151following::
    152152
    153153    for message in user.get_and_delete_messages():
    154154        ...
    155    
     155
    156156with::
    157157
    158158    from django.contrib import messages
    159159    for message in messages.get_messages(request):
    160160        ...
    161    
    162 For more information, see the full 
    163 :ref:`messages documentation <ref-contrib-messages>`. You should begin to 
     161
     162For more information, see the full
     163:ref:`messages documentation <ref-contrib-messages>`. You should begin to
    164164update your code to use the new API immediately.
    165165
    166166What's new in Django 1.2
     
    239239          class="highlight"
    240240        {% endif %}
    241241      >{{ message }}</div>
     242
     243Natural keys in fixtures
     244------------------------
     245
     246Fixtures can refer to remote objects using :ref:`natural keys`. This
     247lookup scheme is an alternative to the normal primary-key based object
     248references in a fixture, improving readability, and resolving problems
     249referring to objects whose primary key value may not be predictable or
     250known.
     251
  • docs/topics/serialization.txt

    diff -r e0fb67da6521 docs/topics/serialization.txt
    a b  
    154154.. _PyYAML: http://www.pyyaml.org/
    155155
    156156Notes for specific serialization formats
    157 ----------------------------------------
     157~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    158158
    159159json
    160 ~~~~
     160^^^^
    161161
    162162If you're using UTF-8 (or any other non-ASCII encoding) data with the JSON
    163163serializer, you must pass ``ensure_ascii=False`` as a parameter to the
     
    191191
    192192.. _special encoder: http://svn.red-bean.com/bob/simplejson/tags/simplejson-1.7/docs/index.html
    193193
     194Natural keys
     195------------
     196
     197The default serialization strategy for foreign keys and many-to-many
     198relations is to serialize the value of the primary key(s) of the
     199objects in the relation. This strategy works well for most types of
     200object, but it can cause difficulty in some circumstances.
     201
     202Consider the case of a list of objects that have foreign key on
     203:model:`ContentType`. If you're going to serialize an object that
     204refers to a content type, you need to have a way to refer to that
     205content type. Content Types are automatically created by Django as
     206part of the database synchronization process, so you don't need to
     207include content types in a fixture or other serialized data. As a
     208result, the primary key of any given content type isn't easy to
     209predict - it will depend on how and when :djadmin:`syncdb` was
     210executed to create the content types.
     211
     212There is also the matter of convenience. An integer id isn't always
     213the most convenient way to refer to an object; sometimes, a
     214more natural reference would be helpful.
     215
     216Deserialization of natural keys
     217~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     218
     219It is for these reasons that Django provides `natural keys`. A natural
     220key is a tuple of values that can be used to uniquely identify an
     221object instance without using the primary key value itself.
     222
     223Consider the following two models::
     224
     225    from django.db import models
     226
     227    class Person(models.Model):
     228        first_name = models.CharField(max_length=100)
     229        last_name = models.CharField(max_length=100)
     230
     231        birthdate = models.DateField()
     232
     233    class Book(models.Model):
     234        name = models.CharField(max_length=100)
     235        author = models.ForeignKey(Person)
     236
     237Ordinarily, serialized data for ``Book`` would use an integer to refer to
     238the author. For example, in JSON, a Book might be serialized as::
     239
     240    ...
     241    {
     242        "pk": 1,
     243        "model": "store.book",
     244        "fields": {
     245            "name": "Mostly Harmless",
     246            "author": 42
     247        }
     248    }
     249    ...
     250
     251This isn't a particularly natural way to refer to an author. It
     252requires that you know the primary key value for the author; it also
     253requires that this primary key value is stable and predictable.
     254
     255However, if we add natural key handling to Person, the fixture becomes
     256much more humane. To add natural key handling, you define a default
     257Manager for Person with a ``get_by_natural_key()`` method. In the case
     258of a Person, a good natural key might be the pair of first and last
     259name::
     260
     261    from django.db import models
     262
     263    class PersonManager(models.Manager):
     264        def get_by_natural_key(self, first_name, last_name):
     265            return self.filter(first_name=first_name, last_name=last_name)
     266
     267    class Person(models.Model):
     268        objects = PersonManager()
     269
     270        first_name = models.CharField(max_length=100)
     271        last_name = models.CharField(max_length=100)
     272
     273        birthdate = models.DateField()
     274
     275Now Books can use that natural key to refer to Person objects::
     276
     277    ...
     278    {
     279        "pk": 1,
     280        "model": "store.book",
     281        "fields": {
     282            "name": "Mostly Harmless",
     283            "author": ["Douglas", "Adams"]
     284        }
     285    }
     286    ...
     287
     288When you try to load this serialized data, Django will use the
     289``get_by_natural_key()`` method to resolve ``["Douglas", "Adams"]``
     290into a primary key of an actual ``Person`` object.
     291
     292Serialization of natural keys
     293~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     294
     295So how do you get Django to emit a natural key when serializing an object?
     296You add another method -- this time to the model itself::
     297
     298    class Person(models.Model):
     299        objects = PersonManager()
     300
     301        first_name = models.CharField(max_length=100)
     302        last_name = models.CharField(max_length=100)
     303
     304        birthdate = models.DateField()
     305
     306        def natural_key(self):
     307            return (self.first_name, self.last_name)
     308
     309When a model has a ``natural_key`` method, Django will use that method
     310to serialize any reference to objects of that type.
     311
     312.. note::
     313
     314    You don't need to define both ``natural_key()`` and
     315    ``get_by_natural_key()``. If you don't want Django to output
     316    natural keys during serialization, but you want to retain the
     317    ability to load natural keys, then you can opt to not implement
     318    the ``natural_key()`` method.
     319
     320    Conversely, if (for some strange reason) you want Django to output
     321    natural keys during serialization, but *not* be able to load those
     322    key values, just don't define the ``get_by_natural_key()`` method.
     323
     324Dependencies during serialization
     325~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     326
     327Since natural keys rely on database lookups to resolve references, it
     328is important that data exists before it is referenced. You can't make
     329a `forward reference` with natural keys - the data you are referencing
     330must exist before you include a natural key reference to that data.
     331
     332To accommodate this limitation, calls to :djadmin:`dumpdata` will
     333serialize any model with a ``natural_key()`` method before it serializes
     334normal key objects.
     335
     336However, this may not always be enough. If your natural key refers to
     337another object (by using a foreign key or natural key to another object
     338as part of a natural key), then you need to be able to ensure that
     339the objects on which a natural key depends occur in the serialized data
     340before the natural key requires them.
     341
     342To control this ordering, you can define dependencies on your
     343``natural_key()`` methods. You do this by setting a ``dependencies``
     344attribute on the ``natural_key()`` method itself.
     345
     346For example, consider the ``Permission`` model in ``contrib.auth``.
     347The following is a simplified version of the ``Permission`` model::
     348
     349    class Permission(models.Model):
     350        name = models.CharField(max_length=50)
     351        content_type = models.ForeignKey(ContentType)
     352        codename = models.CharField(max_length=100)
     353        # ...
     354        def natural_key(self):
     355            return (self.codename,) + self.content_type.natural_key()
     356
     357The natural key for a ``Permission`` is a combination of the codename for the
     358``Permission``, and the ``ContentType`` to which the ``Permission`` applies. This means
     359that ``ContentType``s must be serialized before ``Permission``s. To define this
     360dependency, we add one extra line::
     361
     362    class Permission(models.Model):
     363        # ...
     364        def natural_key(self):
     365            return (self.codename,) + self.content_type.natural_key()
     366        natural_key.dependencies = ['contenttypes.contenttype']
     367
     368This definition ensures that ``ContentType`` models are serialized before
     369``Permission`` models. In turn, any object referencing ``Permission`` will
     370be serialized after both ``ContentType`` and ``Permission``.
  • new file tests/modeltests/fixtures/fixtures/fixture6.json

    diff -r e0fb67da6521 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 e0fb67da6521 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            <natural>fixtures</natural>
     7            <natural>article</natural>
     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            <natural>fixtures</natural>
     15            <natural>article</natural>
     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            <natural>fixtures</natural>
     23            <natural>article</natural>
     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 e0fb67da6521 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 e0fb67da6521 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">
     5            <natural>Stephane Grappelli</natural>
     6        </field>
     7        <field to="auth.permission" name="permissions" rel="ManyToManyRel">
     8            <object>
     9                <natural>add_user</natural>
     10                <natural>auth</natural>
     11                <natural>user</natural>
     12            </object>
     13            <object>
     14                <natural>delete_user</natural>
     15                <natural>auth</natural>
     16                <natural>user</natural>
     17            </object>
     18        </field>
     19    </object>
     20    <object pk="3" model="fixtures.person">
     21        <field type="CharField" name="name">
     22            <natural>Artist formerly known as &quot;Prince&quot;</natural>
     23        </field>
     24    </object>
     25    <object pk="3" model="fixtures.visa">
     26        <field type="CharField" name="person">
     27            <natural>Artist formerly known as &quot;Prince&quot;</natural>
     28        </field>
     29        <field to="auth.permission" name="permissions" rel="ManyToManyRel">
     30            <object>
     31                <natural>change_user</natural>
     32                <natural>auth</natural>
     33                <natural>user</natural>
     34            </object>
     35        </field>
     36    </object>
     37    <object pk="1" model="fixtures.book">
     38        <field type="CharField" name="name">Music for all ages</field>
     39        <field to="fixtures.person" name="authors" rel="ManyToManyRel">
     40            <object>
     41                <natural>Django Reinhardt</natural>
     42            </object>
     43            <object>
     44                <natural>Artist formerly known as &quot;Prince&quot;</natural>
     45            </object>
     46        </field>
     47    </object>
     48</django-objects>
  • tests/modeltests/fixtures/models.py

    diff -r e0fb67da6521 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_natural_key(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 natural_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"><natural>fixtures</natural><natural>article</natural></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"><natural>fixtures</natural><natural>article</natural></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"><natural>fixtures</natural><natural>article</natural></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"><natural>fixtures</natural><natural>article</natural></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"><natural>Django Reinhardt</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>add_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>change_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>delete_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="2" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Stephane Grappelli</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>add_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>delete_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="3" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Artist formerly known as "Prince"</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>change_user</natural><natural>auth</natural><natural>user</natural></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><natural>Artist formerly known as "Prince"</natural></object><object><natural>Django Reinhardt</natural></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"><natural>fixtures</natural><natural>article</natural></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"><natural>fixtures</natural><natural>article</natural></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 e0fb67da6521 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
  • new file tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json

    diff -r e0fb67da6521 tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json
    - +  
     1[
     2    {
     3        "pk": 12,
     4        "model": "fixtures_regress.person",
     5        "fields": {
     6            "name": "Greg Egan"
     7        }
     8    },
     9    {
     10        "pk": 11,
     11        "model": "fixtures_regress.store",
     12        "fields": {
     13            "name": "Angus and Robertson"
     14        }
     15    },
     16    {
     17        "pk": 10,
     18        "model": "fixtures_regress.book",
     19        "fields": {
     20            "name": "Permutation City",
     21            "author": 12,
     22            "stores": [11]
     23        }
     24    }
     25]
     26 No newline at end of file
  • new file tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml

    diff -r e0fb67da6521 tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2<django-objects version="1.0">
     3    <object pk="22" model="fixtures_regress.person">
     4        <field type="CharField" name="name">Orson Scott Card</field>
     5    </object>
     6    <object pk="21" model="fixtures_regress.store">
     7        <field type="CharField" name="name">Collins Bookstore</field>
     8    </object>
     9    <object pk="20" model="fixtures_regress.book">
     10        <field type="CharField" name="name">Ender's Game</field>
     11        <field to="fixtures_regress.person" name="author" rel="ManyToOneRel">22</field>
     12        <field to="fixtures_regress.store" name="stores" rel="ManyToManyRel">
     13            <object pk="21"/>
     14        </field>
     15    </object>
     16</django-objects>
     17 No newline at end of file
  • tests/regressiontests/fixtures_regress/models.py

    diff -r e0fb67da6521 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 natural keys
     83
     84class TestManager(models.Manager):
     85    def get_by_natural_key(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 natural_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 natural_key(self):
     114        return (self.name,)
     115    natural_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    def __unicode__(self):
     126        return u'%s by %s (available at %s)' % (
     127            self.name,
     128            self.author.name,
     129            ', '.join(s.name for s in self.stores.all())
     130        )
     131
    76132__test__ = {'API_TESTS':"""
    77133>>> from django.core import management
    78134
     
    192248>>> management.call_command('dumpdata', 'fixtures_regress', format='json')
    193249[{"pk": 1, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]
    194250
     251###############################################
     252# Check that natural key requirements are taken into account
     253# when serializing models
     254>>> management.call_command('loaddata', 'forward_ref_lookup.json', verbosity=0)
     255
     256>>> management.call_command('dumpdata', 'fixtures_regress.book', 'fixtures_regress.person', 'fixtures_regress.store', verbosity=0)
     257[{"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"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]
     258
     259# Now lets check the dependency sorting explicitly
     260
     261# First Some models with pathological circular dependencies
     262>>> class Circle1(models.Model):
     263...     name = models.CharField(max_length=255)
     264...     def natural_key(self):
     265...         return self.name
     266...     natural_key.dependencies = ['fixtures_regress.circle2']
     267
     268>>> class Circle2(models.Model):
     269...     name = models.CharField(max_length=255)
     270...     def natural_key(self):
     271...         return self.name
     272...     natural_key.dependencies = ['fixtures_regress.circle1']
     273
     274>>> class Circle3(models.Model):
     275...     name = models.CharField(max_length=255)
     276...     def natural_key(self):
     277...         return self.name
     278...     natural_key.dependencies = ['fixtures_regress.circle3']
     279
     280>>> class Circle4(models.Model):
     281...     name = models.CharField(max_length=255)
     282...     def natural_key(self):
     283...         return self.name
     284...     natural_key.dependencies = ['fixtures_regress.circle5']
     285
     286>>> class Circle5(models.Model):
     287...     name = models.CharField(max_length=255)
     288...     def natural_key(self):
     289...         return self.name
     290...     natural_key.dependencies = ['fixtures_regress.circle6']
     291
     292>>> class Circle6(models.Model):
     293...     name = models.CharField(max_length=255)
     294...     def natural_key(self):
     295...         return self.name
     296...     natural_key.dependencies = ['fixtures_regress.circle4']
     297
     298>>> class ExternalDependency(models.Model):
     299...     name = models.CharField(max_length=255)
     300...     def natural_key(self):
     301...         return self.name
     302...     natural_key.dependencies = ['fixtures_regress.book']
     303
     304# It doesn't matter what order you mention the models
     305# Store *must* be serialized before then Person, and both
     306# must be serialized before Book.
     307>>> from django.core.management.commands.dumpdata import sort_dependencies
     308>>> sort_dependencies([('fixtures_regress', [Book, Person, Store])])
     309[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     310
     311>>> sort_dependencies([('fixtures_regress', [Book, Store, Person])])
     312[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     313
     314>>> sort_dependencies([('fixtures_regress', [Store, Book, Person])])
     315[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     316
     317>>> sort_dependencies([('fixtures_regress', [Store, Person, Book])])
     318[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     319
     320>>> sort_dependencies([('fixtures_regress', [Person, Book, Store])])
     321[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     322
     323>>> sort_dependencies([('fixtures_regress', [Person, Store, Book])])
     324[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     325
     326# A dangling dependency - assume the user knows what they are doing.
     327>>> sort_dependencies([('fixtures_regress', [Person, Circle1, Store, Book])])
     328[<class 'regressiontests.fixtures_regress.models.Circle1'>, <class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>]
     329
     330# A tight circular dependency
     331>>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Store, Book])])
     332Traceback (most recent call last):
     333...
     334CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.
     335
     336>>> sort_dependencies([('fixtures_regress', [Circle1, Book, Circle2])])
     337Traceback (most recent call last):
     338...
     339CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.
     340
     341# A self referential dependency
     342>>> sort_dependencies([('fixtures_regress', [Book, Circle3])])
     343Traceback (most recent call last):
     344...
     345CommandError: Can't resolve dependencies for fixtures_regress.Circle3 in serialized app list.
     346
     347# A long circular dependency
     348>>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Circle3, Store, Book])])
     349Traceback (most recent call last):
     350...
     351CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2, fixtures_regress.Circle3 in serialized app list.
     352
     353# A dependency on a normal, non-natural-key model
     354>>> sort_dependencies([('fixtures_regress', [Person, ExternalDependency, Book])])
     355[<class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.ExternalDependency'>]
     356
     357###############################################
     358# Check that normal primary keys still work
     359# on a model with natural key capabilities
     360
     361>>> management.call_command('loaddata', 'non_natural_1.json', verbosity=0)
     362>>> management.call_command('loaddata', 'non_natural_2.xml', verbosity=0)
     363
     364>>> Book.objects.all()
     365[<Book: Cryptonomicon by Neal Stephenson (available at Amazon, Borders)>, <Book: Ender's Game by Orson Scott Card (available at Collins Bookstore)>, <Book: Permutation City by Greg Egan (available at Angus and Robertson)>]
     366
    195367"""}
     368
Back to Top