Author: SmileyChris
Date: 2010-12-20 22:40:08 -0600 (Mon, 20 Dec 2010)
New Revision: 14994

Modified:
   django/trunk/django/core/serializers/base.py
   django/trunk/django/core/serializers/python.py
   django/trunk/django/core/serializers/xml_serializer.py
   django/trunk/docs/topics/serialization.txt
   django/trunk/tests/regressiontests/serializers_regress/models.py
   django/trunk/tests/regressiontests/serializers_regress/tests.py
Log:
Fixes #13252 -- Use the natural key instead of the primary key when serializing

Modified: django/trunk/django/core/serializers/base.py
===================================================================
--- django/trunk/django/core/serializers/base.py        2010-12-21 01:36:09 UTC 
(rev 14993)
+++ django/trunk/django/core/serializers/base.py        2010-12-21 04:40:08 UTC 
(rev 14994)
@@ -170,3 +170,21 @@
         # prevent a second (possibly accidental) call to save() from saving
         # the m2m data twice.
         self.m2m_data = None
+
+def build_instance(Model, data, db):
+    """
+    Build a model instance.
+
+    If the model instance doesn't have a primary key and the model supports
+    natural keys, try to retrieve it from the database.
+    """
+    obj = Model(**data)
+    if obj.pk is None and hasattr(Model, 'natural_key') and\
+            hasattr(Model._default_manager, 'get_by_natural_key'):
+        pk = obj.natural_key()
+        try:
+            obj.pk = Model._default_manager.db_manager(db)\
+                                           .get_by_natural_key(*pk).pk
+        except Model.DoesNotExist:
+            pass
+    return obj

Modified: django/trunk/django/core/serializers/python.py
===================================================================
--- django/trunk/django/core/serializers/python.py      2010-12-21 01:36:09 UTC 
(rev 14993)
+++ django/trunk/django/core/serializers/python.py      2010-12-21 04:40:08 UTC 
(rev 14994)
@@ -27,11 +27,13 @@
         self._current = {}
 
     def end_object(self, obj):
-        self.objects.append({
-            "model"  : smart_unicode(obj._meta),
-            "pk"     : smart_unicode(obj._get_pk_val(), strings_only=True),
-            "fields" : self._current
-        })
+        data = {
+            "model": smart_unicode(obj._meta),
+            "fields": self._current
+        }
+        if not self.use_natural_keys or not hasattr(obj, 'natural_key'):
+            data['pk'] = smart_unicode(obj._get_pk_val(), strings_only=True)
+        self.objects.append(data)
         self._current = None
 
     def handle_field(self, obj, field):
@@ -82,7 +84,9 @@
     for d in object_list:
         # Look up the model and starting build a dict of data for it.
         Model = _get_model(d["model"])
-        data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
+        data = {}
+        if 'pk' in d:
+            data[Model._meta.pk.attname] = Model._meta.pk.to_python(d['pk'])
         m2m_data = {}
 
         # Handle each field
@@ -127,8 +131,10 @@
             else:
                 data[field.name] = field.to_python(field_value)
 
-        yield base.DeserializedObject(Model(**data), m2m_data)
+        obj = base.build_instance(Model, data, db)
 
+        yield base.DeserializedObject(obj, m2m_data)
+
 def _get_model(model_identifier):
     """
     Helper to look up a model from an "app_label.module_name" string.

Modified: django/trunk/django/core/serializers/xml_serializer.py
===================================================================
--- django/trunk/django/core/serializers/xml_serializer.py      2010-12-21 
01:36:09 UTC (rev 14993)
+++ django/trunk/django/core/serializers/xml_serializer.py      2010-12-21 
04:40:08 UTC (rev 14994)
@@ -42,17 +42,13 @@
             raise base.SerializationError("Non-model object (%s) encountered 
during serialization" % type(obj))
 
         self.indent(1)
-        obj_pk = obj._get_pk_val()
-        if obj_pk is None:
-            attrs = {"model": smart_unicode(obj._meta),}
-        else:
-            attrs = {
-                "pk": smart_unicode(obj._get_pk_val()),
-                "model": smart_unicode(obj._meta),
-            }
+        object_data = {"model": smart_unicode(obj._meta)}
+        if not self.use_natural_keys or not hasattr(obj, 'natural_key'):
+            obj_pk = obj._get_pk_val()
+            if obj_pk is not None:
+                object_data['pk'] = smart_unicode(obj_pk)
+        self.xml.startElement("object", object_data)
 
-        self.xml.startElement("object", attrs)
-
     def end_object(self, obj):
         """
         Called after handling all fields for an object.
@@ -173,14 +169,11 @@
         Model = self._get_model_from_node(node, "model")
 
         # Start building a data dictionary from the object.
-        # If the node is missing the pk set it to None
-        if node.hasAttribute("pk"):
-            pk = node.getAttribute("pk")
-        else:
-            pk = None
+        data = {}
+        if node.hasAttribute('pk'):
+            data[Model._meta.pk.attname] = Model._meta.pk.to_python(
+                                                    node.getAttribute('pk'))
 
-        data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
-
         # Also start building a dict of m2m data (this is saved as
         # {m2m_accessor_attribute : [list_of_related_objects]})
         m2m_data = {}
@@ -210,8 +203,10 @@
                     value = field.to_python(getInnerText(field_node).strip())
                 data[field.name] = value
 
+        obj = base.build_instance(Model, data, self.db)
+
         # Return a DeserializedObject so that the m2m data has a place to live.
-        return base.DeserializedObject(Model(**data), m2m_data)
+        return base.DeserializedObject(obj, m2m_data)
 
     def _handle_fk_field_node(self, node, field):
         """

Modified: django/trunk/docs/topics/serialization.txt
===================================================================
--- django/trunk/docs/topics/serialization.txt  2010-12-21 01:36:09 UTC (rev 
14993)
+++ django/trunk/docs/topics/serialization.txt  2010-12-21 04:40:08 UTC (rev 
14994)
@@ -307,6 +307,12 @@
     fields will be effectively unique, you can still use those fields
     as a natural key.
 
+.. versionchanged:: 1.3
+
+Deserialization of objects with no primary key will always check whether the
+model's manager has a ``get_by_natural_key()`` method and if so, use it to
+populate the deserialized object's primary key.
+
 Serialization of natural keys
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -353,6 +359,23 @@
     natural keys during serialization, but *not* be able to load those
     key values, just don't define the ``get_by_natural_key()`` method.
 
+.. versionchanged:: 1.3
+
+When ``use_natural_keys=True`` is specified, the primary key is no longer
+provided in the serialized data of this object since it can be calculated
+during deserialization::
+
+    ...
+    {
+        "model": "store.person",
+        "fields": {
+            "first_name": "Douglas",
+            "last_name": "Adams",
+            "birth_date": "1952-03-11",
+        }
+    }
+    ...
+
 Dependencies during serialization
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

Modified: django/trunk/tests/regressiontests/serializers_regress/models.py
===================================================================
--- django/trunk/tests/regressiontests/serializers_regress/models.py    
2010-12-21 01:36:09 UTC (rev 14993)
+++ django/trunk/tests/regressiontests/serializers_regress/models.py    
2010-12-21 04:40:08 UTC (rev 14994)
@@ -264,3 +264,17 @@
 
     def __len__(self):
         return self.data
+
+#Tests for natural keys.
+class BookManager(models.Manager):
+    def get_by_natural_key(self, isbn13):
+        return self.get(isbn13=isbn13)
+
+class Book(models.Model):
+    isbn13 = models.CharField(max_length=14)
+    title = models.CharField(max_length=100)
+
+    objects = BookManager()
+
+    def natural_key(self):
+        return (self.isbn13,)

Modified: django/trunk/tests/regressiontests/serializers_regress/tests.py
===================================================================
--- django/trunk/tests/regressiontests/serializers_regress/tests.py     
2010-12-21 01:36:09 UTC (rev 14993)
+++ django/trunk/tests/regressiontests/serializers_regress/tests.py     
2010-12-21 04:40:08 UTC (rev 14994)
@@ -414,8 +414,36 @@
     self.assertEqual(string_data, stream.getvalue())
     stream.close()
 
+def naturalKeyTest(format, self):
+    book1 = {'isbn13': '978-1590597255', 'title': 'The Definitive Guide to '
+             'Django: Web Development Done Right'}
+    book2 = {'isbn13':'978-1590599969', 'title': 'Practical Django Projects'}
+
+    # Create the books.
+    adrian = Book.objects.create(**book1)
+    james = Book.objects.create(**book2)
+
+    # Serialize the books.
+    string_data = serializers.serialize(format, Book.objects.all(), indent=2,
+                                        use_natural_keys=True)
+
+    # Delete one book (to prove that the natural key generation will only
+    # restore the primary keys of books found in the database via the
+    # get_natural_key manager method).
+    james.delete()
+
+    # Deserialize and test.
+    books = list(serializers.deserialize(format, string_data))
+    self.assertEqual(len(books), 2)
+    self.assertEqual(books[0].object.title, book1['title'])
+    self.assertEqual(books[0].object.pk, adrian.pk)
+    self.assertEqual(books[1].object.title, book2['title'])
+    self.assertEqual(books[1].object.pk, None)
+
 for format in serializers.get_serializer_formats():
     setattr(SerializerTests, 'test_' + format + '_serializer', 
curry(serializerTest, format))
     setattr(SerializerTests, 'test_' + format + '_serializer_fields', 
curry(fieldsTest, format))
+    setattr(SerializerTests, 'test_' + format + '_serializer_natural_key',
+            curry(naturalKeyTest, format))
     if format != 'python':
         setattr(SerializerTests, 'test_' + format + '_serializer_stream', 
curry(streamTest, format))

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to