Ticket #13252: 13252-natural-key-serializing-r16406.diff

File 13252-natural-key-serializing-r16406.diff, 39.1 KB (added by Claude Paroz, 8 years ago)

Updated to apply cleanly on current trunk

  • django/core/management/commands/dumpdata.py

    diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py
    index 706bf60..8d8b63e 100644
    a b class Command(BaseCommand): 
    1919            help='An appname or appname.ModelName to exclude (use multiple --exclude to exclude multiple apps/models).'),
    2020        make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False,
    2121            help='Use natural keys if they are available.'),
     22        make_option('--natural-foreign', action='store_true', dest='use_natural_foreign_keys', default=False,
     23            help='Use natural foreign keys if they are available.'),
     24        make_option('--natural-primary', action='store_true', dest='use_natural_primary_keys', default=False,
     25            help='Use natural primary keys if they are available.'),
    2226        make_option('-a', '--all', action='store_true', dest='use_base_manager', default=False,
    2327            help="Use Django's base manager to dump all models stored in the database, including those that would otherwise be filtered or modified by a custom manager."),
    2428    )
    class Command(BaseCommand): 
    3741        excludes = options.get('exclude',[])
    3842        show_traceback = options.get('traceback', False)
    3943        use_natural_keys = options.get('use_natural_keys', False)
     44        if use_natural_keys:
     45            # raise pending deprecation warning.
     46            pass
     47        use_natural_foreign_keys = options.get('use_natural_foreign_keys', False) or use_natural_keys
     48        use_natural_primary_keys = options.get('use_natural_primary_keys', False)
    4049        use_base_manager = options.get('use_base_manager', False)
    4150
    4251        excluded_apps = set()
    class Command(BaseCommand): 
    111120
    112121        try:
    113122            return serializers.serialize(format, objects, indent=indent,
    114                         use_natural_keys=use_natural_keys)
     123                        use_natural_foreign_keys=use_natural_foreign_keys,
     124                        use_natural_primary_keys=use_natural_primary_keys)
    115125        except Exception, e:
    116126            if show_traceback:
    117127                raise
  • django/core/serializers/base.py

    diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
    index 6afaf21..e9b0f42 100644
    a b class Serializer(object): 
    3838        self.stream = options.pop("stream", StringIO())
    3939        self.selected_fields = options.pop("fields", None)
    4040        self.use_natural_keys = options.pop("use_natural_keys", False)
     41        if self.use_natural_keys:
     42            # raise pending deprecation warning.
     43            pass
     44        self.use_natural_foreign_keys = options.pop('use_natural_foreign_keys', False)
     45        self.use_natural_primary_keys = options.pop('use_natural_primary_keys', False)
    4146
    4247        self.start_serialization()
    4348        for obj in queryset:
    class DeserializedObject(object): 
    174179        # prevent a second (possibly accidental) call to save() from saving
    175180        # the m2m data twice.
    176181        self.m2m_data = None
     182
     183def build_instance(Model, data, db):
     184    """
     185    Build a model instance.
     186
     187    If the model instance doesn't have a primary key and the model supports
     188    natural keys, try to retrieve it from the database.
     189    """
     190    obj = Model(**data)
     191    if obj.pk is None and hasattr(Model, 'natural_key') and\
     192            hasattr(Model._default_manager, 'get_by_natural_key'):
     193        pk = obj.natural_key()
     194        try:
     195            obj.pk = Model._default_manager.db_manager(db)\
     196                                           .get_by_natural_key(*pk).pk
     197        except Model.DoesNotExist:
     198            pass
     199    return obj
  • django/core/serializers/python.py

    diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
    index a68ea21..7b839f0 100644
    a b class Serializer(base.Serializer): 
    2727        self._current = {}
    2828
    2929    def end_object(self, obj):
    30         self.objects.append({
    31             "model"  : smart_unicode(obj._meta),
    32             "pk"     : smart_unicode(obj._get_pk_val(), strings_only=True),
    33             "fields" : self._current
    34         })
     30        if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
     31            data = {
     32                "model": smart_unicode(obj._meta),
     33                "pk": smart_unicode(obj._get_pk_val(), strings_only=True),
     34                "fields": self._current,
     35            }
     36        else:
     37            data = {
     38                "model": smart_unicode(obj._meta),
     39                "fields": self._current
     40            }
     41        self.objects.append(data)
    3542        self._current = None
    3643
    3744    def handle_field(self, obj, field):
    class Serializer(base.Serializer): 
    4754    def handle_fk_field(self, obj, field):
    4855        related = getattr(obj, field.name)
    4956        if related is not None:
    50             if self.use_natural_keys and hasattr(related, 'natural_key'):
     57            if self.use_natural_foreign_keys and hasattr(related, 'natural_key'):
    5158                related = related.natural_key()
    5259            else:
    5360                if field.rel.field_name == related._meta.pk.name:
    class Serializer(base.Serializer): 
    6067
    6168    def handle_m2m_field(self, obj, field):
    6269        if field.rel.through._meta.auto_created:
    63             if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
     70            if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'):
    6471                m2m_value = lambda value: value.natural_key()
    6572            else:
    6673                m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True)
    def Deserializer(object_list, **options): 
    8289    for d in object_list:
    8390        # Look up the model and starting build a dict of data for it.
    8491        Model = _get_model(d["model"])
    85         data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
     92        data = {}
     93        if 'pk' in d:
     94            data[Model._meta.pk.attname] = Model._meta.pk.to_python(d['pk'])
    8695        m2m_data = {}
    8796
    8897        # Handle each field
    def Deserializer(object_list, **options): 
    127136            else:
    128137                data[field.name] = field.to_python(field_value)
    129138
    130         yield base.DeserializedObject(Model(**data), m2m_data)
     139        obj = base.build_instance(Model, data, db)
     140
     141        yield base.DeserializedObject(obj, m2m_data)
    131142
    132143def _get_model(model_identifier):
    133144    """
  • django/core/serializers/xml_serializer.py

    diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
    index bcf5631..22205d9 100644
    a b class Serializer(base.Serializer): 
    4242            raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
    4343
    4444        self.indent(1)
    45         obj_pk = obj._get_pk_val()
    46         if obj_pk is None:
    47             attrs = {"model": smart_unicode(obj._meta),}
    48         else:
    49             attrs = {
    50                 "pk": smart_unicode(obj._get_pk_val()),
    51                 "model": smart_unicode(obj._meta),
    52             }
    53 
    54         self.xml.startElement("object", attrs)
     45        object_data = {"model": smart_unicode(obj._meta)}
     46        if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
     47            obj_pk = obj._get_pk_val()
     48            if obj_pk is not None:
     49                object_data['pk'] = smart_unicode(obj_pk)
     50        self.xml.startElement("object", object_data)
    5551
    5652    def end_object(self, obj):
    5753        """
    class Serializer(base.Serializer): 
    8783        self._start_relational_field(field)
    8884        related = getattr(obj, field.name)
    8985        if related is not None:
    90             if self.use_natural_keys and hasattr(related, 'natural_key'):
     86            if self.use_natural_foreign_keys and hasattr(related, 'natural_key'):
    9187                # If related object has a natural key, use it
    9288                related = related.natural_key()
    9389                # Iterable natural keys are rolled out as subelements
    class Serializer(base.Serializer): 
    115111        """
    116112        if field.rel.through._meta.auto_created:
    117113            self._start_relational_field(field)
    118             if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
     114            if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'):
    119115                # If the objects in the m2m have a natural key, use it
    120116                def handle_m2m(value):
    121117                    natural = value.natural_key()
    class Deserializer(base.Deserializer): 
    173169        Model = self._get_model_from_node(node, "model")
    174170
    175171        # Start building a data dictionary from the object.
    176         # If the node is missing the pk set it to None
    177         if node.hasAttribute("pk"):
    178             pk = node.getAttribute("pk")
    179         else:
    180             pk = None
    181 
    182         data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
     172        data = {}
     173        if node.hasAttribute('pk'):
     174            data[Model._meta.pk.attname] = Model._meta.pk.to_python(
     175                                                    node.getAttribute('pk'))
    183176
    184177        # Also start building a dict of m2m data (this is saved as
    185178        # {m2m_accessor_attribute : [list_of_related_objects]})
    class Deserializer(base.Deserializer): 
    210203                    value = field.to_python(getInnerText(field_node).strip())
    211204                data[field.name] = value
    212205
     206        obj = base.build_instance(Model, data, self.db)
     207
    213208        # Return a DeserializedObject so that the m2m data has a place to live.
    214         return base.DeserializedObject(Model(**data), m2m_data)
     209        return base.DeserializedObject(obj, m2m_data)
    215210
    216211    def _handle_fk_field_node(self, node, field):
    217212        """
  • docs/topics/serialization.txt

    diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
    index f0f17b2..06ac347 100644
    a b into the primary key of an actual ``Person`` object. 
    313313    fields will be effectively unique, you can still use those fields
    314314    as a natural key.
    315315
     316.. versionadded:: 1.4
     317
     318Deserialization of objects with no primary key will always check whether the
     319model's manager has a ``get_by_natural_key()`` method and if so, use it to
     320populate the deserialized object's primary key.
     321
    316322Serialization of natural keys
    317323~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    318324
    Firstly, you need to add another method -- this time to the model itself:: 
    335341
    336342That method should always return a natural key tuple -- in this
    337343example, ``(first name, last name)``. Then, when you call
    338 ``serializers.serialize()``, you provide a ``use_natural_keys=True``
    339 argument::
    340 
    341     >>> serializers.serialize('json', [book1, book2], indent=2, use_natural_keys=True)
    342 
    343 When ``use_natural_keys=True`` is specified, Django will use the
    344 ``natural_key()`` method to serialize any reference to objects of the
    345 type that defines the method.
     344``serializers.serialize()``, you provide ``use_natural_foreign_keys=True``
     345or ``use_natural_primary_keys=True`` arguments::
     346
     347    >>> serializers.serialize('json', [book1, book2], indent=2, use_natural_foreign_keys=True, use_natural_primary_keys=True)
     348
     349When ``use_natural_foreign_keys=True`` is specified, Django will use the
     350``natural_key()`` method to serialize any foreign key reference to objects
     351of the type that defines the method.
     352
     353When ``use_natural_primary_keys=True``is specified, Django will not provide the
     354primary key in the serialized data of this object since it can be calculated
     355during deserialization::
     356
     357        ...
     358        {
     359            "model": "store.person",
     360            "fields": {
     361                "first_name": "Douglas",
     362                "last_name": "Adams",
     363                "birth_date": "1952-03-11",
     364            }
     365        }
     366        ...
     367
     368This can be useful when you need to load serialized data into an existing
     369database and you cannot guarantee that the serialized primary key value is not
     370already in use, and do not need to ensure that deserialized objects retain the
     371same primary keys.
    346372
    347373If you are using :djadmin:`dumpdata` to generate serialized data, you
    348 use the `--natural` command line flag to generate natural keys.
     374use the `--natural-foreign` and `--natural-primary` command line flags to
     375generate natural keys.
    349376
    350377.. note::
    351378
    use the `--natural` command line flag to generate natural keys. 
    359386    natural keys during serialization, but *not* be able to load those
    360387    key values, just don't define the ``get_by_natural_key()`` method.
    361388
     389.. versionchanged:: 1.4
     390
     391Previously there was only a ``use_natural_keys`` argument for
     392``serializers.serialize()`` and the `-n` or `--natural` command line flags.
     393These have been deprecated in favor of the ``use_natural_foreign_keys`` and
     394``use_natural_primary_keys`` arguments, and the corresponding
     395`--natural-foreign` and `--natural-primary` command line flags.
     396
     397The original argument and command line flags remain for backwards
     398compatibility, and map to the new ``use_natural_foreign_keys`` argument and
     399`--natural-foreign` command line flag.
     400
    362401Dependencies during serialization
    363402~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    364403
  • tests/modeltests/fixtures/tests.py

    diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py
    index 31f72f0..8d1adb7 100644
    a b class TestCaseFixtureLoadingTests(TestCase): 
    2525
    2626class FixtureLoadingTests(TestCase):
    2727
    28     def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
     28    def _dumpdata_assert(self, args, output, format='json',
     29                         natural_foreign_keys=False, natural_primary_keys=False,
    2930                         use_base_manager=False, exclude_list=[]):
    3031        new_io = StringIO.StringIO()
    3132        management.call_command('dumpdata', *args, **{'format':format,
    3233                                                      'stdout':new_io,
    3334                                                      'stderr':new_io,
    34                                                       'use_natural_keys':natural_keys,
     35                                                      'use_natural_foreign_keys':natural_foreign_keys,
     36                                                      'use_natural_primary_keys':natural_primary_keys,
    3537                                                      'use_base_manager':use_base_manager,
    3638                                                      'exclude': exclude_list})
    3739        command_output = new_io.getvalue().strip()
    class FixtureLoadingTests(TestCase): 
    149151        # By default, you get raw keys on dumpdata
    150152        self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [3, 1]}}]')
    151153
    152         # But you can get natural keys if you ask for them and they are available
    153         self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True)
     154        # But you can get natural foreign keys if you ask for them and they are available
     155        self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_foreign_keys=True)
     156
     157        # You can also omit the primary keys for models that we can get later with natural keys.
     158        self._dumpdata_assert(['fixtures.person'], '[{"fields": {"name": "Artist formerly known as \\"Prince\\""}, "model": "fixtures.person"}, {"fields": {"name": "Django Reinhardt"}, "model": "fixtures.person"}, {"fields": {"name": "Stephane Grappelli"}, "model": "fixtures.person"}]', natural_primary_keys=True)
    154159
    155160        # Dump the current contents of the database as a JSON fixture
    156         self._dumpdata_assert(['fixtures'], '[{"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"]]}}]', natural_keys=True)
     161        self._dumpdata_assert(['fixtures'], '[{"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"]]}}]', natural_foreign_keys=True)
    157162
    158163        # Dump the current contents of the database as an XML fixture
    159164        self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
    160 <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>""", format='xml', natural_keys=True)
     165<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>""", format='xml', natural_foreign_keys=True)
    161166
    162167    def test_dumpdata_with_excludes(self):
    163168        # Load fixture1 which has a site, two articles, and a category
    class FixtureLoadingTests(TestCase): 
    289294        ])
    290295
    291296        # Dump the current contents of the database as a JSON fixture
    292         self._dumpdata_assert(['fixtures'], '[{"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"}}]', natural_keys=True)
     297        self._dumpdata_assert(['fixtures'], '[{"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"}}]', natural_foreign_keys=True)
    293298
    294299        # Dump the current contents of the database as an XML fixture
    295300        self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
    296 <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>""", format='xml', natural_keys=True)
     301<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>""", format='xml', natural_foreign_keys=True)
    297302
    298303class FixtureTransactionTests(TransactionTestCase):
    299304    def _dumpdata_assert(self, args, output, format='json'):
  • tests/regressiontests/fixtures_regress/tests.py

    diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py
    index 3dc4ede..b43f4c8 100644
    a b class NaturalKeyFixtureTests(TestCase): 
    451451            'fixtures_regress.store',
    452452            verbosity=0,
    453453            format='json',
    454             use_natural_keys=True,
     454            use_natural_foreign_keys=True,
     455            use_natural_primary_keys=True,
    455456            stdout=stdout,
    456457        )
    457458        self.assertEqual(
    458459            stdout.getvalue(),
    459             """[{"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"]}}]"""
     460            """[{"fields": {"name": "Amazon"}, "model": "fixtures_regress.store"}, {"fields": {"name": "Borders"}, "model": "fixtures_regress.store"}, {"fields": {"name": "Neal Stephenson"}, "model": "fixtures_regress.person"}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]"""
    460461        )
    461462
    462463    def test_dependency_sorting(self):
  • tests/regressiontests/serializers_regress/models.py

    diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
    index b3ae1fe..780c0cd 100644
    a b class LengthModel(models.Model): 
    264264
    265265    def __len__(self):
    266266        return self.data
     267
     268#Tests for natural keys.
     269class BookManager(models.Manager):
     270    def get_by_natural_key(self, isbn13):
     271        return self.get(isbn13=isbn13)
     272
     273class Book(models.Model):
     274    isbn13 = models.CharField(max_length=14)
     275    title = models.CharField(max_length=100)
     276
     277    objects = BookManager()
     278
     279    def natural_key(self):
     280        return (self.isbn13,)
  • tests/regressiontests/serializers_regress/tests.py

    diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
    index 90a438c..116a8f7 100644
    a b def streamTest(format, self): 
    436436    self.assertEqual(string_data, stream.getvalue())
    437437    stream.close()
    438438
     439def naturalKeyTest(format, self):
     440    book1 = {'isbn13': '978-1590597255', 'title': 'The Definitive Guide to '
     441             'Django: Web Development Done Right'}
     442    book2 = {'isbn13':'978-1590599969', 'title': 'Practical Django Projects'}
     443
     444    # Create the books.
     445    adrian = Book.objects.create(**book1)
     446    james = Book.objects.create(**book2)
     447
     448    # Serialize the books.
     449    string_data = serializers.serialize(format, Book.objects.all(), indent=2,
     450                                        use_natural_foreign_keys=True,
     451                                        use_natural_primary_keys=True)
     452
     453    # Delete one book (to prove that the natural key generation will only
     454    # restore the primary keys of books found in the database via the
     455    # get_natural_key manager method).
     456    james.delete()
     457
     458    # Deserialize and test.
     459    books = list(serializers.deserialize(format, string_data))
     460    self.assertEqual(len(books), 2)
     461    self.assertEqual(books[0].object.title, book1['title'])
     462    self.assertEqual(books[0].object.pk, adrian.pk)
     463    self.assertEqual(books[1].object.title, book2['title'])
     464    self.assertEqual(books[1].object.pk, None)
     465
    439466for format in serializers.get_serializer_formats():
    440467    setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
    441468    setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
     469    setattr(SerializerTests, 'test_' + format + '_serializer_natural_keys', curry(naturalKeyTest, format))
    442470    if format != 'python':
    443471        setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format))
Back to Top