Fix #17879: Corrected regression in python (inherited by yaml and json) serializer that prevented serializing model instances with null FK ref to a model when serializing with natural keys. Thanks danfairs and tmitchell.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17685 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Karen Tracey 2012-03-12 19:41:31 +00:00
parent 2f520139be
commit 20c69c5e51
4 changed files with 60 additions and 2 deletions

View File

@ -369,6 +369,7 @@ answer newbie questions, and generally made Django that much better:
Slawek Mikula <slawek dot mikula at gmail dot com> Slawek Mikula <slawek dot mikula at gmail dot com>
Shawn Milochik <shawn@milochik.com> Shawn Milochik <shawn@milochik.com>
mitakummaa@gmail.com mitakummaa@gmail.com
Taylor Mitchell <taylor.mitchell@gmail.com>
mmarshall mmarshall
Andreas Mock <andreas.mock@web.de> Andreas Mock <andreas.mock@web.de>
Reza Mohammadi <reza@zeerak.ir> Reza Mohammadi <reza@zeerak.ir>

View File

@ -47,7 +47,10 @@ class Serializer(base.Serializer):
def handle_fk_field(self, obj, field): def handle_fk_field(self, obj, field):
if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'): if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
related = getattr(obj, field.name) related = getattr(obj, field.name)
if related:
value = related.natural_key() value = related.natural_key()
else:
value = None
else: else:
value = getattr(obj, field.get_attname()) value = getattr(obj, field.get_attname())
self._current[field.name] = value self._current[field.name] = value

View File

@ -111,6 +111,18 @@ class Anchor(models.Model):
class Meta: class Meta:
ordering = ('id',) 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): class UniqueAnchor(models.Model):
"""This is a model that can be used as """This is a model that can be used as
something for other models to point at""" something for other models to point at"""
@ -120,6 +132,9 @@ class UniqueAnchor(models.Model):
class FKData(models.Model): class FKData(models.Model):
data = models.ForeignKey(Anchor, null=True) data = models.ForeignKey(Anchor, null=True)
class FKDataNaturalKey(models.Model):
data = models.ForeignKey(NaturalKeyAnchor, null=True)
class M2MData(models.Model): class M2MData(models.Model):
data = models.ManyToManyField(Anchor, null=True) data = models.ManyToManyField(Anchor, null=True)
@ -272,3 +287,4 @@ class LengthModel(models.Model):
def __len__(self): def __len__(self):
return self.data return self.data

View File

@ -42,7 +42,8 @@ from .models import (BooleanData, CharData, DateData, DateTimeData, EmailData,
PositiveSmallIntegerPKData, SlugPKData, SmallPKData, USStatePKData, PositiveSmallIntegerPKData, SlugPKData, SmallPKData, USStatePKData,
AutoNowDateTimeData, ModifyingSaveData, InheritAbstractModel, BaseModel, AutoNowDateTimeData, ModifyingSaveData, InheritAbstractModel, BaseModel,
ExplicitInheritBaseModel, InheritBaseModel, ProxyBaseModel, 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 # A set of functions that can be used to recreate
# test data objects of various kinds. # test data objects of various kinds.
@ -353,6 +354,12 @@ The end."""),
(data_obj, 1005, LengthModel, 1), (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 # 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 # when field.empty_strings_allowed is True and the value is None; skip these
# tests. # tests.
@ -452,6 +459,35 @@ def serializerTest(format, self):
for klass, count in instance_count.items(): for klass, count in instance_count.items():
self.assertEqual(count, klass.objects.count()) 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): def fieldsTest(format, self):
obj = ComplexModel(field1='first', field2='second', field3='third') obj = ComplexModel(field1='first', field2='second', field3='third')
obj.save_base(raw=True) obj.save_base(raw=True)
@ -482,6 +518,8 @@ def streamTest(format, self):
for format in serializers.get_serializer_formats(): for format in serializers.get_serializer_formats():
setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) 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)) setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
if format != 'python': if format != 'python':
setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format))