Commit b60d5df0 authored by Chris Beaven's avatar Chris Beaven
Browse files

Fixes #13252 -- Use the natural key instead of the primary key when serializing

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14994 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent c2657d89
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -170,3 +170,21 @@ class DeserializedObject(object):
        # 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
+13 −7
Original line number Diff line number Diff line
@@ -27,11 +27,13 @@ class Serializer(base.Serializer):
        self._current = {}

    def end_object(self, obj):
        self.objects.append({
        data = {
            "model": smart_unicode(obj._meta),
            "pk"     : smart_unicode(obj._get_pk_val(), strings_only=True),
            "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 @@ def Deserializer(object_list, **options):
    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,7 +131,9 @@ def Deserializer(object_list, **options):
            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):
    """
+13 −18
Original line number Diff line number Diff line
@@ -42,16 +42,12 @@ class Serializer(base.Serializer):
            raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))

        self.indent(1)
        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 None:
            attrs = {"model": smart_unicode(obj._meta),}
        else:
            attrs = {
                "pk": smart_unicode(obj._get_pk_val()),
                "model": smart_unicode(obj._meta),
            }

        self.xml.startElement("object", attrs)
            if obj_pk is not None:
                object_data['pk'] = smart_unicode(obj_pk)
        self.xml.startElement("object", object_data)

    def end_object(self, obj):
        """
@@ -173,13 +169,10 @@ class Deserializer(base.Deserializer):
        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 = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
        data = {}
        if node.hasAttribute('pk'):
            data[Model._meta.pk.attname] = Model._meta.pk.to_python(
                                                    node.getAttribute('pk'))

        # Also start building a dict of m2m data (this is saved as
        # {m2m_accessor_attribute : [list_of_related_objects]})
@@ -210,8 +203,10 @@ class Deserializer(base.Deserializer):
                    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):
        """
+23 −0
Original line number Diff line number Diff line
@@ -307,6 +307,12 @@ into the primary key of an actual ``Person`` object.
    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 @@ use the `--natural` command line flag to generate natural keys.
    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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+14 −0
Original line number Diff line number Diff line
@@ -264,3 +264,17 @@ class LengthModel(models.Model):

    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,)
Loading