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

File 13252-natural-key-serializing-r14992.diff, 8.8 KB (added by Tai Lee, 9 years ago)
  • 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_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.
     
    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

     
    170170        # prevent a second (possibly accidental) call to save() from saving
    171171        # the m2m data twice.
    172172        self.m2m_data = None
     173
     174def build_instance(Model, data, db):
     175    """
     176    Build a model instance.
     177
     178    If the model instance doesn't have a primary key and the model supports
     179    natural keys, try to retrieve it from the database.
     180    """
     181    obj = Model(**data)
     182    if obj.pk is None and hasattr(Model, 'natural_key') and\
     183            hasattr(Model._default_manager, 'get_by_natural_key'):
     184        pk = obj.natural_key()
     185        try:
     186            obj.pk = Model._default_manager.db_manager(db)\
     187                                           .get_by_natural_key(*pk).pk
     188        except Model.DoesNotExist:
     189            pass
     190    return obj
  • 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        data = {
     31            "model": smart_unicode(obj._meta),
     32            "fields": self._current
     33        }
     34        if not self.use_natural_keys or not hasattr(obj, 'natural_key'):
     35            data['pk'] = smart_unicode(obj._get_pk_val(), strings_only=True)
     36        self.objects.append(data)
    3537        self._current = None
    3638
    3739    def handle_field(self, obj, field):
     
    8284    for d in object_list:
    8385        # Look up the model and starting build a dict of data for it.
    8486        Model = _get_model(d["model"])
    85         data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
     87        data = {}
     88        if 'pk' in d:
     89            data[Model._meta.pk.attname] = Model._meta.pk.to_python(d['pk'])
    8690        m2m_data = {}
    8791
    8892        # Handle each field
     
    127131            else:
    128132                data[field.name] = field.to_python(field_value)
    129133
    130         yield base.DeserializedObject(Model(**data), m2m_data)
     134        obj = base.build_instance(Model, data, db)
    131135
     136        yield base.DeserializedObject(obj, m2m_data)
     137
    132138def _get_model(model_identifier):
    133139    """
    134140    Helper to look up a model from an "app_label.module_name" string.
  • 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_keys=True)
     429
     430    # Delete one book (to prove that the natural key generation will only
     431    # restore the primary keys of books found in the database via the
     432    # get_natural_key manager method).
     433    james.delete()
     434
     435    # Deserialize and test.
     436    books = list(serializers.deserialize(format, string_data))
     437    self.assertEqual(len(books), 2)
     438    self.assertEqual(books[0].object.title, book1['title'])
     439    self.assertEqual(books[0].object.pk, adrian.pk)
     440    self.assertEqual(books[1].object.title, book2['title'])
     441    self.assertEqual(books[1].object.pk, None)
     442
    417443for format in serializers.get_serializer_formats():
    418444    setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
    419445    setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
     446    setattr(SerializerTests, 'test_' + format + '_serializer_natural_key',
     447            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,)
  • 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
     
    353359    natural keys during serialization, but *not* be able to load those
    354360    key values, just don't define the ``get_by_natural_key()`` method.
    355361
     362.. versionadded:: 1.3
     363
     364When ``use_natural_keys=True`` is specified, the primary key is no longer
     365provided in the serialized data of this object since it can be calculated
     366during deserialization::
     367
     368    ...
     369    {
     370        "model": "store.person",
     371        "fields": {
     372            "first_name": "Douglas",
     373            "last_name": "Adams",
     374            "birth_date": "1952-03-11",
     375        }
     376    }
     377    ...
     378
    356379Dependencies during serialization
    357380~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    358381
Back to Top