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

File 13252-natural-key-serializing-r14995.diff, 39.7 KB (added by Tai Lee, 13 years ago)
  • django/core/serializers/json.py

     
    2121        self.options.pop('stream', None)
    2222        self.options.pop('fields', None)
    2323        self.options.pop('use_natural_keys', None)
     24        self.options.pop('use_natural_foreign_keys', None)
     25        self.options.pop('use_natural_primary_keys', None)
    2426        simplejson.dump(self.objects, self.stream, cls=DjangoJSONEncoder, **self.options)
    2527
    2628    def getvalue(self):
  • django/core/serializers/xml_serializer.py

     
    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             }
     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)
    5351
    54         self.xml.startElement("object", attrs)
    55 
    5652    def end_object(self, obj):
    5753        """
    5854        Called after handling all fields for an object.
     
    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
     
    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()
     
    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
     172        data = {}
     173        if node.hasAttribute('pk'):
     174            data[Model._meta.pk.attname] = Model._meta.pk.to_python(
     175                                                    node.getAttribute('pk'))
    181176
    182         data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
    183 
    184177        # Also start building a dict of m2m data (this is saved as
    185178        # {m2m_accessor_attribute : [list_of_related_objects]})
    186179        m2m_data = {}
     
    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        """
  • django/core/serializers/base.py

     
    3434        self.stream = options.get("stream", StringIO())
    3535        self.selected_fields = options.get("fields")
    3636        self.use_natural_keys = options.get("use_natural_keys", False)
     37        if self.use_natural_keys:
     38            # raise pending deprecation warning.
     39            pass
     40        self.use_natural_foreign_keys = options.get("use_natural_foreign_keys", False) or self.use_natural_keys
     41        self.use_natural_primary_keys = options.get("use_natural_primary_keys", False)
    3742
    3843        self.start_serialization()
    3944        for obj in queryset:
     
    170175        # prevent a second (possibly accidental) call to save() from saving
    171176        # the m2m data twice.
    172177        self.m2m_data = None
     178
     179def build_instance(Model, data, db):
     180    """
     181    Build a model instance.
     182
     183    If the model instance doesn't have a primary key and the model supports
     184    natural keys, try to retrieve it from the database.
     185    """
     186    obj = Model(**data)
     187    if obj.pk is None and hasattr(Model, 'natural_key') and\
     188            hasattr(Model._default_manager, 'get_by_natural_key'):
     189        pk = obj.natural_key()
     190        try:
     191            obj.pk = Model._default_manager.db_manager(db)\
     192                                           .get_by_natural_key(*pk).pk
     193        except Model.DoesNotExist:
     194            pass
     195    return obj
  • django/core/serializers/pyyaml.py

     
    4141        self.options.pop('stream', None)
    4242        self.options.pop('fields', None)
    4343        self.options.pop('use_natural_keys', None)
     44        self.options.pop('use_natural_foreign_keys', None)
     45        self.options.pop('use_natural_primary_keys', None)
    4446        yaml.dump(self.objects, self.stream, Dumper=DjangoSafeDumper, **self.options)
    4547
    4648    def getvalue(self):
  • django/core/serializers/python.py

     
    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):
     
    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:
     
    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)
     
    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
     
    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)
    131140
     141        yield base.DeserializedObject(obj, m2m_data)
     142
    132143def _get_model(model_identifier):
    133144    """
    134145    Helper to look up a model from an "app_label.module_name" string.
  • django/core/management/commands/dumpdata.py

     
    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    )
     
    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()
     
    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
  • tests/modeltests/fixtures/tests.py

     
    2424
    2525class FixtureLoadingTests(TestCase):
    2626
    27     def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
     27    def _dumpdata_assert(self, args, output, format='json',
     28                         natural_foreign_keys=False, natural_primary_keys=False,
    2829                         use_base_manager=False, exclude_list=[]):
    2930        new_io = StringIO.StringIO()
    3031        management.call_command('dumpdata', *args, **{'format':format,
    3132                                                      'stdout':new_io,
    3233                                                      'stderr':new_io,
    33                                                       'use_natural_keys':natural_keys,
     34                                                      'use_natural_foreign_keys':natural_foreign_keys,
     35                                                      'use_natural_primary_keys':natural_primary_keys,
    3436                                                      'use_base_manager':use_base_manager,
    3537                                                      'exclude': exclude_list})
    3638        command_output = new_io.getvalue().strip()
     
    147149        # By default, you get raw keys on dumpdata
    148150        self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [3, 1]}}]')
    149151
    150         # But you can get natural keys if you ask for them and they are available
    151         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)
     152        # But you can get natural foreign 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_foreign_keys=True)
    152154
     155        # You can also omit the primary keys for models that we can get later with natural keys.
     156        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)
     157
    153158        # Dump the current contents of the database as a JSON fixture
    154         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)
     159        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)
    155160
    156161        # Dump the current contents of the database as an XML fixture
    157162        self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
    158 <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)
     163<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)
    159164
    160165    def test_dumpdata_with_excludes(self):
    161166        # Load fixture1 which has a site, two articles, and a category
     
    286291        ])
    287292
    288293        # Dump the current contents of the database as a JSON fixture
    289         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)
     294        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)
    290295
    291296        # Dump the current contents of the database as an XML fixture
    292297        self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
    293 <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)
     298<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)
    294299
    295300class FixtureTransactionTests(TransactionTestCase):
    296301    def _dumpdata_assert(self, args, output, format='json'):
  • tests/regressiontests/serializers_regress/tests.py

     
    414414    self.assertEqual(string_data, stream.getvalue())
    415415    stream.close()
    416416
     417def naturalKeyTest(format, self):
     418    book1 = {'isbn13': '978-1590597255', 'title': 'The Definitive Guide to '
     419             'Django: Web Development Done Right'}
     420    book2 = {'isbn13':'978-1590599969', 'title': 'Practical Django Projects'}
     421
     422    # Create the books.
     423    adrian = Book.objects.create(**book1)
     424    james = Book.objects.create(**book2)
     425
     426    # Serialize the books.
     427    string_data = serializers.serialize(format, Book.objects.all(), indent=2,
     428                                        use_natural_foreign_keys=True,
     429                                        use_natural_primary_keys=True)
     430
     431    # Delete one book (to prove that the natural key generation will only
     432    # restore the primary keys of books found in the database via the
     433    # get_natural_key manager method).
     434    james.delete()
     435
     436    # Deserialize and test.
     437    books = list(serializers.deserialize(format, string_data))
     438    self.assertEqual(len(books), 2)
     439    self.assertEqual(books[0].object.title, book1['title'])
     440    self.assertEqual(books[0].object.pk, adrian.pk)
     441    self.assertEqual(books[1].object.title, book2['title'])
     442    self.assertEqual(books[1].object.pk, None)
     443
    417444for format in serializers.get_serializer_formats():
    418445    setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
    419446    setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
     447    setattr(SerializerTests, 'test_' + format + '_serializer_natural_keys', curry(naturalKeyTest, format))
    420448    if format != 'python':
    421449        setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format))
  • tests/regressiontests/serializers_regress/models.py

     
    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/fixtures_regress/tests.py

     
    450450            'fixtures_regress.store',
    451451            verbosity=0,
    452452            format='json',
    453             use_natural_keys=True,
     453            use_natural_foreign_keys=True,
     454            use_natural_primary_keys=True,
    454455            stdout=stdout,
    455456        )
    456457        self.assertEqual(
    457458            stdout.getvalue(),
    458             """[{"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"]}}]"""
     459            """[{"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"]}}]"""
    459460        )
    460461
    461462    def test_dependency_sorting(self):
  • docs/topics/serialization.txt

     
    307307    fields will be effectively unique, you can still use those fields
    308308    as a natural key.
    309309
     310.. versionadded:: 1.3
     311
     312Deserialization of objects with no primary key will always check whether the
     313model's manager has a ``get_by_natural_key()`` method and if so, use it to
     314populate the deserialized object's primary key.
     315
    310316Serialization of natural keys
    311317~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    312318
     
    329335
    330336That method should always return a natural key tuple -- in this
    331337example, ``(first name, last name)``. Then, when you call
    332 ``serializers.serialize()``, you provide a ``use_natural_keys=True``
    333 argument::
     338``serializers.serialize()``, you provide ``use_natural_foreign_keys=True``
     339or ``use_natural_primary_keys=True`` arguments::
    334340
    335     >>> serializers.serialize('json', [book1, book2], indent=2, use_natural_keys=True)
     341    >>> serializers.serialize('json', [book1, book2], indent=2, use_natural_foreign_keys=True, use_natural_primary_keys=True)
    336342
    337 When ``use_natural_keys=True`` is specified, Django will use the
    338 ``natural_key()`` method to serialize any reference to objects of the
    339 type that defines the method.
     343When ``use_natural_foreign_keys=True`` is specified, Django will use the
     344``natural_key()`` method to serialize any foreign key reference to objects
     345of the type that defines the method.
    340346
     347When ``use_natural_primary_keys=True``is specified, Django will not provide the
     348primary key in the serialized data of this object since it can be calculated
     349during deserialization::
     350
     351        ...
     352        {
     353            "model": "store.person",
     354            "fields": {
     355                "first_name": "Douglas",
     356                "last_name": "Adams",
     357                "birth_date": "1952-03-11",
     358            }
     359        }
     360        ...
     361
     362This can be useful when you need to load serialized data into an existing
     363database and you cannot guarantee that the serialized primary key value is not
     364already in use, and do not need to ensure that deserialized objects retain the
     365same primary keys.
     366
    341367If you are using :djadmin:`dumpdata` to generate serialized data, you
    342 use the `--natural` command line flag to generate natural keys.
     368use the `--natural-foreign` and `--natural-primary` command line flags to
     369generate natural keys.
    343370
    344371.. note::
    345372
     
    353380    natural keys during serialization, but *not* be able to load those
    354381    key values, just don't define the ``get_by_natural_key()`` method.
    355382
     383.. versionchanged:: 1.3
     384
     385Previously there was only a ``use_natural_keys`` argument for
     386``serializers.serialize()`` and the `-n` or `--natural` command line flags.
     387These have been deprecated in favor of the ``use_natural_foreign_keys`` and
     388``use_natural_primary_keys`` arguments, and the corresponding
     389`--natural-foreign` and `--natural-primary` command line flags.
     390
     391The original argument and command line flags remain for backwards
     392compatibility, and map to the new ``use_natural_foreign_keys`` argument and
     393`--natural-foreign` command line flag.
     394
    356395Dependencies during serialization
    357396~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    358397
Back to Top