diff --git a/AUTHORS b/AUTHORS index 5fa21d3f8e..82f77585f3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -369,6 +369,7 @@ answer newbie questions, and generally made Django that much better: Slawek Mikula Shawn Milochik mitakummaa@gmail.com + Taylor Mitchell mmarshall Andreas Mock Reza Mohammadi diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 6cdfc9f240..195bf11d24 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -47,7 +47,10 @@ class Serializer(base.Serializer): def handle_fk_field(self, obj, field): if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'): related = getattr(obj, field.name) - value = related.natural_key() + if related: + value = related.natural_key() + else: + value = None else: value = getattr(obj, field.get_attname()) self._current[field.name] = value diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py index f366f90806..52223e72d8 100644 --- a/tests/regressiontests/serializers_regress/models.py +++ b/tests/regressiontests/serializers_regress/models.py @@ -111,6 +111,18 @@ class Anchor(models.Model): class Meta: ordering = ('id',) +class NaturalKeyAnchorManager(models.Manager): + def get_by_natural_key(self, data): + return self.get(data=data) + +class NaturalKeyAnchor(models.Model): + objects = NaturalKeyAnchorManager() + + data = models.CharField(max_length=100, unique=True) + + def natural_key(self): + return (self.data,) + class UniqueAnchor(models.Model): """This is a model that can be used as something for other models to point at""" @@ -120,6 +132,9 @@ class UniqueAnchor(models.Model): class FKData(models.Model): data = models.ForeignKey(Anchor, null=True) +class FKDataNaturalKey(models.Model): + data = models.ForeignKey(NaturalKeyAnchor, null=True) + class M2MData(models.Model): data = models.ManyToManyField(Anchor, null=True) @@ -272,3 +287,4 @@ class LengthModel(models.Model): def __len__(self): return self.data + diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index 704e34b8ba..65194da428 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -42,7 +42,8 @@ from .models import (BooleanData, CharData, DateData, DateTimeData, EmailData, PositiveSmallIntegerPKData, SlugPKData, SmallPKData, USStatePKData, AutoNowDateTimeData, ModifyingSaveData, InheritAbstractModel, BaseModel, ExplicitInheritBaseModel, InheritBaseModel, ProxyBaseModel, - ProxyProxyBaseModel, BigIntegerData, LengthModel, Tag, ComplexModel) + ProxyProxyBaseModel, BigIntegerData, LengthModel, Tag, ComplexModel, + NaturalKeyAnchor, FKDataNaturalKey) # A set of functions that can be used to recreate # test data objects of various kinds. @@ -353,6 +354,12 @@ The end."""), (data_obj, 1005, LengthModel, 1), ] +natural_key_test_data = [ + (data_obj, 1100, NaturalKeyAnchor, "Natural Key Anghor"), + (fk_obj, 1101, FKDataNaturalKey, 1100), + (fk_obj, 1102, FKDataNaturalKey, None), +] + # Because Oracle treats the empty string as NULL, Oracle is expected to fail # when field.empty_strings_allowed is True and the value is None; skip these # tests. @@ -452,6 +459,35 @@ def serializerTest(format, self): for klass, count in instance_count.items(): self.assertEqual(count, klass.objects.count()) +def naturalKeySerializerTest(format, self): + # Create all the objects defined in the test data + objects = [] + instance_count = {} + for (func, pk, klass, datum) in natural_key_test_data: + with connection.constraint_checks_disabled(): + objects.extend(func[0](pk, klass, datum)) + + # Get a count of the number of objects created for each class + for klass in instance_count: + instance_count[klass] = klass.objects.count() + + # Serialize the test database + serialized_data = serializers.serialize(format, objects, indent=2, + use_natural_keys=True) + + for obj in serializers.deserialize(format, serialized_data): + obj.save() + + # Assert that the deserialized data is the same + # as the original source + for (func, pk, klass, datum) in natural_key_test_data: + func[1](self, pk, klass, datum) + + # Assert that the number of objects deserialized is the + # same as the number that was serialized. + for klass, count in instance_count.items(): + self.assertEqual(count, klass.objects.count()) + def fieldsTest(format, self): obj = ComplexModel(field1='first', field2='second', field3='third') obj.save_base(raw=True) @@ -482,6 +518,8 @@ def streamTest(format, self): for format in serializers.get_serializer_formats(): setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) + setattr(SerializerTests, 'test_' + format + '_natural_key_serializer', curry(naturalKeySerializerTest, format)) setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format)) if format != 'python': setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) +