From cb7860ccedb199cb221c9e084b5104978b246356 Mon Sep 17 00:00:00 2001 From: Denys Duchier Date: Sat, 11 Feb 2017 06:40:15 -0500 Subject: [PATCH] Fixed #24607 -- Serialized natural keys in multi-table inheritance models. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks João Paulo Melo de Sampaio for the test. --- django/core/serializers/base.py | 10 ++++++++- tests/serializers/models/__init__.py | 1 + tests/serializers/models/multi_table.py | 19 ++++++++++++++++ tests/serializers/test_natural.py | 29 ++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/serializers/models/multi_table.py diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index def5146850..cfe618eb34 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -82,8 +82,16 @@ class Serializer: # Use the concrete parent class' _meta instead of the object's _meta # This is to avoid local_fields problems for proxy models. Refs #17717. concrete_model = obj._meta.concrete_model + # When using natural primary keys, retrieve the pk field of the + # parent for multi-table inheritance child models. That field must + # be serialized, otherwise deserialization isn't possible. + if self.use_natural_primary_keys: + pk = concrete_model._meta.pk + pk_parent = pk if pk.remote_field and pk.remote_field.parent_link else None + else: + pk_parent = None for field in concrete_model._meta.local_fields: - if field.serialize: + if field.serialize or field is pk_parent: if field.remote_field is None: if self.selected_fields is None or field.attname in self.selected_fields: self.handle_field(obj, field) diff --git a/tests/serializers/models/__init__.py b/tests/serializers/models/__init__.py index a786b54be9..9ac2381d17 100644 --- a/tests/serializers/models/__init__.py +++ b/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 diff --git a/tests/serializers/models/multi_table.py b/tests/serializers/models/multi_table.py new file mode 100644 index 0000000000..e3a5e7fd5f --- /dev/null +++ b/tests/serializers/models/multi_table.py @@ -0,0 +1,19 @@ +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) diff --git a/tests/serializers/test_natural.py b/tests/serializers/test_natural.py index 0c99e8e13f..776f554a1c 100644 --- a/tests/serializers/test_natural.py +++ b/tests/serializers/test_natural.py @@ -2,7 +2,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 @@ -67,6 +67,33 @@ def natural_key_test(self, format): self.assertIsNone(books[1].object.pk) +def natural_pk_mti_test(self, format): + """ + 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)