* Serializers were including all superclass fields in their output. Now only local fields are included. * Implicit OneToOne primary keys were not correctly added to the metamodel, so they were always marked to be serialized, even though they were primary * Model saving was too aggressive about creating new parent class instances during deserialization. Raw save on a model now skips saving of the parent class. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7600 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
1426c24517
commit
12716794db
|
@ -38,7 +38,7 @@ class Serializer(object):
|
||||||
self.start_serialization()
|
self.start_serialization()
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
self.start_object(obj)
|
self.start_object(obj)
|
||||||
for field in obj._meta.fields:
|
for field in obj._meta.local_fields:
|
||||||
if field.serialize:
|
if field.serialize:
|
||||||
if field.rel is None:
|
if field.rel is None:
|
||||||
if self.selected_fields is None or field.attname in self.selected_fields:
|
if self.selected_fields is None or field.attname in self.selected_fields:
|
||||||
|
|
|
@ -290,12 +290,17 @@ class Model(object):
|
||||||
meta = cls._meta
|
meta = cls._meta
|
||||||
signal = False
|
signal = False
|
||||||
|
|
||||||
for parent, field in meta.parents.items():
|
# If we are in a raw save, save the object exactly as presented.
|
||||||
self.save_base(raw, parent)
|
# That means that we don't try to be smart about saving attributes
|
||||||
setattr(self, field.attname, self._get_pk_val(parent._meta))
|
# that might have come from the parent class - we just save the
|
||||||
|
# attributes we have been given to the class we have been given.
|
||||||
|
if not raw:
|
||||||
|
for parent, field in meta.parents.items():
|
||||||
|
self.save_base(raw, parent)
|
||||||
|
setattr(self, field.attname, self._get_pk_val(parent._meta))
|
||||||
|
|
||||||
non_pks = [f for f in meta.local_fields if not f.primary_key]
|
non_pks = [f for f in meta.local_fields if not f.primary_key]
|
||||||
|
|
||||||
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
|
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
|
||||||
pk_val = self._get_pk_val(meta)
|
pk_val = self._get_pk_val(meta)
|
||||||
# Note: the comparison with '' is required for compatibility with
|
# Note: the comparison with '' is required for compatibility with
|
||||||
|
|
|
@ -102,7 +102,7 @@ class Options(object):
|
||||||
# field.
|
# field.
|
||||||
field = self.parents.value_for_index(0)
|
field = self.parents.value_for_index(0)
|
||||||
field.primary_key = True
|
field.primary_key = True
|
||||||
self.pk = field
|
self.setup_pk(field)
|
||||||
else:
|
else:
|
||||||
auto = AutoField(verbose_name='ID', primary_key=True,
|
auto = AutoField(verbose_name='ID', primary_key=True,
|
||||||
auto_created=True)
|
auto_created=True)
|
||||||
|
|
|
@ -63,6 +63,41 @@ be serialized.
|
||||||
doesn't specify all the fields that are required by a model, the deserializer
|
doesn't specify all the fields that are required by a model, the deserializer
|
||||||
will not be able to save deserialized instances.
|
will not be able to save deserialized instances.
|
||||||
|
|
||||||
|
Inherited Models
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you have a model that is defined using an `abstract base class`_, you don't
|
||||||
|
have to do anything special to serialize that model. Just call the serializer
|
||||||
|
on the object (or objects) that you want to serialize, and the output will be
|
||||||
|
a complete representation of the serialized object.
|
||||||
|
|
||||||
|
However, if you have a model that uses `multi-table inheritance`_, you also
|
||||||
|
need to serialize all of the base classes for the model. This is because only
|
||||||
|
the fields that are locally defined on the model will be serialized. For
|
||||||
|
example, consider the following models::
|
||||||
|
|
||||||
|
class Place(models.Model):
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
class Restaurant(Place):
|
||||||
|
serves_hot_dogs = models.BooleanField()
|
||||||
|
|
||||||
|
If you only serialize the Restaurant model::
|
||||||
|
|
||||||
|
data = serializers.serialize('xml', Restaurant.objects.all())
|
||||||
|
|
||||||
|
the fields on the serialized output will only contain the `serves_hot_dogs`
|
||||||
|
attribute. The `name` attribute of the base class will be ignored.
|
||||||
|
|
||||||
|
In order to fully serialize your Restaurant instances, you will need to
|
||||||
|
serialize the Place models as well::
|
||||||
|
|
||||||
|
all_objects = list(Restaurant.objects.all()) + list(Place.objects.all())
|
||||||
|
data = serializers.serialize('xml', all_objects)
|
||||||
|
|
||||||
|
.. _abstract base class: http://www.djangoproject.com/documentation/model-api/#abstract-base-classes
|
||||||
|
.. _multi-table inheritance: http://www.djangoproject.com/documentation/model-api/#multi-table-inheritance
|
||||||
|
|
||||||
Deserializing data
|
Deserializing data
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
@ -147,8 +147,13 @@ Test constructor for Restaurant.
|
||||||
>>> c.save()
|
>>> c.save()
|
||||||
>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c)
|
>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c)
|
||||||
>>> ir.save()
|
>>> ir.save()
|
||||||
|
>>> ItalianRestaurant.objects.filter(address='1234 W. Ash')
|
||||||
|
[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
|
||||||
|
|
||||||
>>> ir.address = '1234 W. Elm'
|
>>> ir.address = '1234 W. Elm'
|
||||||
>>> ir.save()
|
>>> ir.save()
|
||||||
|
>>> ItalianRestaurant.objects.filter(address='1234 W. Elm')
|
||||||
|
[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
|
||||||
|
|
||||||
# Make sure Restaurant and ItalianRestaurant have the right fields in the right
|
# Make sure Restaurant and ItalianRestaurant have the right fields in the right
|
||||||
# order.
|
# order.
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
"""
|
||||||
|
Regression tests for Model inheritance behaviour.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Place(models.Model):
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
address = models.CharField(max_length=80)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s the place" % self.name
|
||||||
|
|
||||||
|
class Restaurant(Place):
|
||||||
|
serves_hot_dogs = models.BooleanField()
|
||||||
|
serves_pizza = models.BooleanField()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s the restaurant" % self.name
|
||||||
|
|
||||||
|
class ItalianRestaurant(Restaurant):
|
||||||
|
serves_gnocchi = models.BooleanField()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s the italian restaurant" % self.name
|
||||||
|
|
||||||
|
class ParkingLot(Place):
|
||||||
|
# An explicit link to the parent (we can control the attribute name).
|
||||||
|
parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
|
||||||
|
capacity = models.IntegerField()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s the parking lot" % self.name
|
||||||
|
|
||||||
|
__test__ = {'API_TESTS':"""
|
||||||
|
# Regression for #7350, #7202
|
||||||
|
# Check that when you create a Parent object with a specific reference to an existent
|
||||||
|
# child instance, saving the Parent doesn't duplicate the child.
|
||||||
|
# This behaviour is only activated during a raw save - it is mostly relevant to
|
||||||
|
# deserialization, but any sort of CORBA style 'narrow()' API would require a
|
||||||
|
# similar approach.
|
||||||
|
|
||||||
|
# Create a child-parent-grandparent chain
|
||||||
|
>>> place1 = Place(name="Guido's House of Pasta", address='944 W. Fullerton')
|
||||||
|
>>> place1.save_base(raw=True)
|
||||||
|
>>> restaurant = Restaurant(place_ptr=place1, serves_hot_dogs=True, serves_pizza=False)
|
||||||
|
>>> restaurant.save_base(raw=True)
|
||||||
|
>>> italian_restaurant = ItalianRestaurant(restaurant_ptr=restaurant, serves_gnocchi=True)
|
||||||
|
>>> italian_restaurant.save_base(raw=True)
|
||||||
|
|
||||||
|
# Create a child-parent chain with an explicit parent link
|
||||||
|
>>> place2 = Place(name='Main St', address='111 Main St')
|
||||||
|
>>> place2.save_base(raw=True)
|
||||||
|
>>> park = ParkingLot(parent=place2, capacity=100)
|
||||||
|
>>> park.save_base(raw=True)
|
||||||
|
|
||||||
|
# Check that no extra parent objects have been created.
|
||||||
|
>>> Place.objects.all()
|
||||||
|
[<Place: Guido's House of Pasta the place>, <Place: Main St the place>]
|
||||||
|
|
||||||
|
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
|
||||||
|
>>> [sorted(d.items()) for d in dicts]
|
||||||
|
[[('name', u"Guido's House of Pasta"), ('serves_hot_dogs', True)]]
|
||||||
|
|
||||||
|
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
|
||||||
|
>>> [sorted(d.items()) for d in dicts]
|
||||||
|
[[('name', u"Guido's House of Pasta"), ('serves_gnocchi', True), ('serves_hot_dogs', True)]]
|
||||||
|
|
||||||
|
>>> dicts = ParkingLot.objects.values('name','capacity')
|
||||||
|
>>> [sorted(d.items()) for d in dicts]
|
||||||
|
[[('capacity', 100), ('name', u'Main St')]]
|
||||||
|
|
||||||
|
# You can also update objects when using a raw save.
|
||||||
|
>>> place1.name = "Guido's All New House of Pasta"
|
||||||
|
>>> place1.save_base(raw=True)
|
||||||
|
|
||||||
|
>>> restaurant.serves_hot_dogs = False
|
||||||
|
>>> restaurant.save_base(raw=True)
|
||||||
|
|
||||||
|
>>> italian_restaurant.serves_gnocchi = False
|
||||||
|
>>> italian_restaurant.save_base(raw=True)
|
||||||
|
|
||||||
|
>>> place2.name='Derelict lot'
|
||||||
|
>>> place2.save_base(raw=True)
|
||||||
|
|
||||||
|
>>> park.capacity = 50
|
||||||
|
>>> park.save_base(raw=True)
|
||||||
|
|
||||||
|
# No extra parent objects after an update, either.
|
||||||
|
>>> Place.objects.all()
|
||||||
|
[<Place: Derelict lot the place>, <Place: Guido's All New House of Pasta the place>]
|
||||||
|
|
||||||
|
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
|
||||||
|
>>> [sorted(d.items()) for d in dicts]
|
||||||
|
[[('name', u"Guido's All New House of Pasta"), ('serves_hot_dogs', False)]]
|
||||||
|
|
||||||
|
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
|
||||||
|
>>> [sorted(d.items()) for d in dicts]
|
||||||
|
[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
|
||||||
|
|
||||||
|
>>> dicts = ParkingLot.objects.values('name','capacity')
|
||||||
|
>>> [sorted(d.items()) for d in dicts]
|
||||||
|
[[('capacity', 50), ('name', u'Derelict lot')]]
|
||||||
|
|
||||||
|
# If you try to raw_save a parent attribute onto a child object,
|
||||||
|
# the attribute will be ignored.
|
||||||
|
|
||||||
|
>>> italian_restaurant.name = "Lorenzo's Pasta Hut"
|
||||||
|
>>> italian_restaurant.save_base(raw=True)
|
||||||
|
|
||||||
|
# Note that the name has not changed
|
||||||
|
# - name is an attribute of Place, not ItalianRestaurant
|
||||||
|
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
|
||||||
|
>>> [sorted(d.items()) for d in dicts]
|
||||||
|
[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
|
||||||
|
|
||||||
|
"""}
|
|
@ -223,3 +223,23 @@ class ModifyingSaveData(models.Model):
|
||||||
"A save method that modifies the data in the object"
|
"A save method that modifies the data in the object"
|
||||||
self.data = 666
|
self.data = 666
|
||||||
super(ModifyingSaveData, self).save(raw)
|
super(ModifyingSaveData, self).save(raw)
|
||||||
|
|
||||||
|
# Tests for serialization of models using inheritance.
|
||||||
|
# Regression for #7202, #7350
|
||||||
|
class AbstractBaseModel(models.Model):
|
||||||
|
parent_data = models.IntegerField()
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
class InheritAbstractModel(AbstractBaseModel):
|
||||||
|
child_data = models.IntegerField()
|
||||||
|
|
||||||
|
class BaseModel(models.Model):
|
||||||
|
parent_data = models.IntegerField()
|
||||||
|
|
||||||
|
class InheritBaseModel(BaseModel):
|
||||||
|
child_data = models.IntegerField()
|
||||||
|
|
||||||
|
class ExplicitInheritBaseModel(BaseModel):
|
||||||
|
parent = models.OneToOneField(BaseModel)
|
||||||
|
child_data = models.IntegerField()
|
||||||
|
|
|
@ -32,7 +32,7 @@ def data_create(pk, klass, data):
|
||||||
instance = klass(id=pk)
|
instance = klass(id=pk)
|
||||||
instance.data = data
|
instance.data = data
|
||||||
models.Model.save_base(instance, raw=True)
|
models.Model.save_base(instance, raw=True)
|
||||||
return instance
|
return [instance]
|
||||||
|
|
||||||
def generic_create(pk, klass, data):
|
def generic_create(pk, klass, data):
|
||||||
instance = klass(id=pk)
|
instance = klass(id=pk)
|
||||||
|
@ -40,32 +40,45 @@ def generic_create(pk, klass, data):
|
||||||
models.Model.save_base(instance, raw=True)
|
models.Model.save_base(instance, raw=True)
|
||||||
for tag in data[1:]:
|
for tag in data[1:]:
|
||||||
instance.tags.create(data=tag)
|
instance.tags.create(data=tag)
|
||||||
return instance
|
return [instance]
|
||||||
|
|
||||||
def fk_create(pk, klass, data):
|
def fk_create(pk, klass, data):
|
||||||
instance = klass(id=pk)
|
instance = klass(id=pk)
|
||||||
setattr(instance, 'data_id', data)
|
setattr(instance, 'data_id', data)
|
||||||
models.Model.save_base(instance, raw=True)
|
models.Model.save_base(instance, raw=True)
|
||||||
return instance
|
return [instance]
|
||||||
|
|
||||||
def m2m_create(pk, klass, data):
|
def m2m_create(pk, klass, data):
|
||||||
instance = klass(id=pk)
|
instance = klass(id=pk)
|
||||||
models.Model.save_base(instance, raw=True)
|
models.Model.save_base(instance, raw=True)
|
||||||
instance.data = data
|
instance.data = data
|
||||||
return instance
|
return [instance]
|
||||||
|
|
||||||
def o2o_create(pk, klass, data):
|
def o2o_create(pk, klass, data):
|
||||||
instance = klass()
|
instance = klass()
|
||||||
instance.data_id = data
|
instance.data_id = data
|
||||||
models.Model.save_base(instance, raw=True)
|
models.Model.save_base(instance, raw=True)
|
||||||
return instance
|
return [instance]
|
||||||
|
|
||||||
def pk_create(pk, klass, data):
|
def pk_create(pk, klass, data):
|
||||||
instance = klass()
|
instance = klass()
|
||||||
instance.data = data
|
instance.data = data
|
||||||
models.Model.save_base(instance, raw=True)
|
models.Model.save_base(instance, raw=True)
|
||||||
return instance
|
return [instance]
|
||||||
|
|
||||||
|
def inherited_create(pk, klass, data):
|
||||||
|
instance = klass(id=pk,**data)
|
||||||
|
# This isn't a raw save because:
|
||||||
|
# 1) we're testing inheritance, not field behaviour, so none
|
||||||
|
# of the field values need to be protected.
|
||||||
|
# 2) saving the child class and having the parent created
|
||||||
|
# automatically is easier than manually creating both.
|
||||||
|
models.Model.save(instance)
|
||||||
|
created = [instance]
|
||||||
|
for klass,field in instance._meta.parents.items():
|
||||||
|
created.append(klass.objects.get(id=pk))
|
||||||
|
return created
|
||||||
|
|
||||||
# A set of functions that can be used to compare
|
# A set of functions that can be used to compare
|
||||||
# test data objects of various kinds
|
# test data objects of various kinds
|
||||||
def data_compare(testcase, pk, klass, data):
|
def data_compare(testcase, pk, klass, data):
|
||||||
|
@ -94,6 +107,11 @@ def pk_compare(testcase, pk, klass, data):
|
||||||
instance = klass.objects.get(data=data)
|
instance = klass.objects.get(data=data)
|
||||||
testcase.assertEqual(data, instance.data)
|
testcase.assertEqual(data, instance.data)
|
||||||
|
|
||||||
|
def inherited_compare(testcase, pk, klass, data):
|
||||||
|
instance = klass.objects.get(id=pk)
|
||||||
|
for key,value in data.items():
|
||||||
|
testcase.assertEqual(value, getattr(instance,key))
|
||||||
|
|
||||||
# Define some data types. Each data type is
|
# Define some data types. Each data type is
|
||||||
# actually a pair of functions; one to create
|
# actually a pair of functions; one to create
|
||||||
# and one to compare objects of that type
|
# and one to compare objects of that type
|
||||||
|
@ -103,6 +121,7 @@ fk_obj = (fk_create, fk_compare)
|
||||||
m2m_obj = (m2m_create, m2m_compare)
|
m2m_obj = (m2m_create, m2m_compare)
|
||||||
o2o_obj = (o2o_create, o2o_compare)
|
o2o_obj = (o2o_create, o2o_compare)
|
||||||
pk_obj = (pk_create, pk_compare)
|
pk_obj = (pk_create, pk_compare)
|
||||||
|
inherited_obj = (inherited_create, inherited_compare)
|
||||||
|
|
||||||
test_data = [
|
test_data = [
|
||||||
# Format: (data type, PK value, Model Class, data)
|
# Format: (data type, PK value, Model Class, data)
|
||||||
|
@ -255,6 +274,10 @@ The end."""),
|
||||||
|
|
||||||
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
|
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
|
||||||
(data_obj, 810, ModifyingSaveData, 42),
|
(data_obj, 810, ModifyingSaveData, 42),
|
||||||
|
|
||||||
|
(inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}),
|
||||||
|
(inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}),
|
||||||
|
(inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}),
|
||||||
]
|
]
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -277,13 +300,19 @@ def serializerTest(format, self):
|
||||||
|
|
||||||
# Create all the objects defined in the test data
|
# Create all the objects defined in the test data
|
||||||
objects = []
|
objects = []
|
||||||
|
instance_count = {}
|
||||||
transaction.enter_transaction_management()
|
transaction.enter_transaction_management()
|
||||||
transaction.managed(True)
|
transaction.managed(True)
|
||||||
for (func, pk, klass, datum) in test_data:
|
for (func, pk, klass, datum) in test_data:
|
||||||
objects.append(func[0](pk, klass, datum))
|
objects.extend(func[0](pk, klass, datum))
|
||||||
|
instance_count[klass] = 0
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
transaction.leave_transaction_management()
|
transaction.leave_transaction_management()
|
||||||
|
|
||||||
|
# Get a count of the number of objects created for each class
|
||||||
|
for klass in instance_count:
|
||||||
|
instance_count[klass] = klass.objects.count()
|
||||||
|
|
||||||
# Add the generic tagged objects to the object list
|
# Add the generic tagged objects to the object list
|
||||||
objects.extend(Tag.objects.all())
|
objects.extend(Tag.objects.all())
|
||||||
|
|
||||||
|
@ -304,6 +333,11 @@ def serializerTest(format, self):
|
||||||
for (func, pk, klass, datum) in test_data:
|
for (func, pk, klass, datum) in test_data:
|
||||||
func[1](self, pk, klass, datum)
|
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.assertEquals(count, klass.objects.count())
|
||||||
|
|
||||||
def fieldsTest(format, self):
|
def fieldsTest(format, self):
|
||||||
# Clear the database first
|
# Clear the database first
|
||||||
management.call_command('flush', verbosity=0, interactive=False)
|
management.call_command('flush', verbosity=0, interactive=False)
|
||||||
|
|
Loading…
Reference in New Issue