Browse Source

Fixed #24607 -- Serialized natural keys in multi-table inheritance models.

João Sampaio 8 years ago
parent
commit
74a575eb72

+ 7 - 1
django/core/serializers/base.py

@@ -88,7 +88,7 @@ class Serializer(object):
                         if self.selected_fields is None or field.attname in self.selected_fields:
                             self.handle_field(obj, field)
                     else:
-                        if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
+                        if self.field_is_selected(field) and self.output_pk_field(obj, field):
                             self.handle_fk_field(obj, field)
             for field in concrete_model._meta.many_to_many:
                 if field.serialize:
@@ -101,6 +101,12 @@ class Serializer(object):
         self.end_serialization()
         return self.getvalue()
 
+    def field_is_selected(self, field):
+        return self.selected_fields is None or field.attname[:-3] in self.selected_fields
+
+    def output_pk_field(self, obj, pk_field):
+        return self.use_natural_primary_keys or pk_field != obj._meta.pk
+
     def start_serialization(self):
         """
         Called when serializing of the queryset starts.

+ 6 - 1
django/db/models/options.py

@@ -13,6 +13,7 @@ from django.db import connections
 from django.db.models import Manager
 from django.db.models.fields import AutoField
 from django.db.models.fields.proxy import OrderWrt
+from django.db.models.fields.related import OneToOneField
 from django.utils import six
 from django.utils.datastructures import ImmutableList, OrderedSet
 from django.utils.deprecation import (
@@ -296,7 +297,11 @@ class Options(object):
     def setup_pk(self, field):
         if not self.pk and field.primary_key:
             self.pk = field
-            field.serialize = False
+            # If the field is a OneToOneField and it's been marked as PK, then
+            # this is a multi-table inheritance PK. It needs to be serialized
+            # to relate the subclass instance to the superclass instance.
+            if not isinstance(field, OneToOneField):
+                field.serialize = False
 
     def setup_proxy(self, target):
         """

+ 1 - 0
tests/serializers/models/__init__.py

@@ -1,3 +1,4 @@
 from .base import *  # NOQA
 from .data import *  # NOQA
+from .multi_table import *  # NOQA
 from .natural import *  # NOQA

+ 22 - 0
tests/serializers/models/multi_table.py

@@ -0,0 +1,22 @@
+from django.db import models
+
+
+class ParentManager(models.Manager):
+    def get_by_natural_key(self, parent_data):
+        return self.get(parent_data=parent_data)
+
+
+class Parent(models.Model):
+    parent_data = models.CharField(max_length=30, unique=True)
+
+    objects = ParentManager()
+
+    def natural_key(self):
+        return (self.parent_data, )
+
+
+class Child(Parent):
+    child_data = models.CharField(max_length=30, unique=True)
+
+    class Meta:
+        manager_inheritance_from_future = True

+ 32 - 1
tests/serializers/test_natural.py

@@ -4,7 +4,7 @@ from django.core import serializers
 from django.db import connection
 from django.test import TestCase
 
-from .models import FKDataNaturalKey, NaturalKeyAnchor
+from .models import Child, FKDataNaturalKey, NaturalKeyAnchor
 from .tests import register_tests
 
 
@@ -69,6 +69,37 @@ def natural_key_test(format, self):
     self.assertIsNone(books[1].object.pk)
 
 
+def natural_pk_mti_test(format, self):
+    """
+    If serializing objects in a multi-table inheritance relationship using
+    natural primary keys, the natural foreign key for the parent is output in
+    the fields of the child so it's possible to relate the child to the parent
+    when deserializing.
+    """
+    child_1 = Child.objects.create(parent_data='1', child_data='1')
+    child_2 = Child.objects.create(parent_data='2', child_data='2')
+
+    string_data = serializers.serialize(
+        format,
+        [child_1.parent_ptr, child_2.parent_ptr, child_2, child_1],
+        use_natural_foreign_keys=True, use_natural_primary_keys=True,
+    )
+
+    child_1.delete()
+    child_2.delete()
+
+    for obj in serializers.deserialize(format, string_data):
+        obj.save()
+
+    children = Child.objects.all()
+    self.assertEqual(len(children), 2)
+    for child in children:
+        # If it's possible to find the superclass from the subclass and it's
+        # the correct superclass, it's working.
+        self.assertEqual(child.child_data, child.parent_data)
+
+
 # Dynamically register tests for each serializer
 register_tests(NaturalKeySerializerTests, 'test_%s_natural_key_serializer', natural_key_serializer_test)
 register_tests(NaturalKeySerializerTests, 'test_%s_serializer_natural_keys', natural_key_test)
+register_tests(NaturalKeySerializerTests, 'test_%s_serializer_natural_pks_mti', natural_pk_mti_test)