Fixed #8134 -- Corrected serialization of m2m fields with intermediate models. Thanks to Rock Howard for the report, and kire for the test case.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@8321 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
dd970bfbfd
commit
63ea57642d
|
@ -49,6 +49,7 @@ class Serializer(base.Serializer):
|
||||||
self._current[field.name] = smart_unicode(related, strings_only=True)
|
self._current[field.name] = smart_unicode(related, strings_only=True)
|
||||||
|
|
||||||
def handle_m2m_field(self, obj, field):
|
def handle_m2m_field(self, obj, field):
|
||||||
|
if field.creates_table:
|
||||||
self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
|
self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
|
||||||
for related in getattr(obj, field.name).iterator()]
|
for related in getattr(obj, field.name).iterator()]
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ class Serializer(base.Serializer):
|
||||||
serialized as references to the object's PK (i.e. the related *data*
|
serialized as references to the object's PK (i.e. the related *data*
|
||||||
is not dumped, just the relation).
|
is not dumped, just the relation).
|
||||||
"""
|
"""
|
||||||
|
if field.creates_table:
|
||||||
self._start_relational_field(field)
|
self._start_relational_field(field)
|
||||||
for relobj in getattr(obj, field.name).iterator():
|
for relobj in getattr(obj, field.name).iterator():
|
||||||
self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
|
self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from django.db import models
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.core import management
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
# Forward declared intermediate model
|
# Forward declared intermediate model
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
person = models.ForeignKey('Person')
|
person = models.ForeignKey('Person')
|
||||||
group = models.ForeignKey('Group')
|
group = models.ForeignKey('Group')
|
||||||
date_joined = models.DateTimeField(default=datetime.now)
|
price = models.IntegerField(default=100)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s is a member of %s" % (self.person.name, self.group.name)
|
return "%s is a member of %s" % (self.person.name, self.group.name)
|
||||||
|
@ -14,7 +15,7 @@ class Membership(models.Model):
|
||||||
class UserMembership(models.Model):
|
class UserMembership(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User)
|
||||||
group = models.ForeignKey('Group')
|
group = models.ForeignKey('Group')
|
||||||
date_joined = models.DateTimeField(default=datetime.now)
|
price = models.IntegerField(default=100)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s is a user and member of %s" % (self.user.username, self.group.name)
|
return "%s is a user and member of %s" % (self.user.username, self.group.name)
|
||||||
|
@ -99,4 +100,141 @@ AttributeError: Cannot use create() on a ManyToManyField which specifies an inte
|
||||||
>>> roll.user_members.all()
|
>>> roll.user_members.all()
|
||||||
[<User: frank>]
|
[<User: frank>]
|
||||||
|
|
||||||
|
# Regression test for #8134 --
|
||||||
|
# m2m-through models shouldn't be serialized as m2m fields on the model.
|
||||||
|
# Dump the current contents of the database as a JSON fixture
|
||||||
|
>>> management.call_command('dumpdata', 'm2m_through_regress', format='json', indent=2)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "m2m_through_regress.membership",
|
||||||
|
"fields": {
|
||||||
|
"person": 1,
|
||||||
|
"price": 100,
|
||||||
|
"group": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "m2m_through_regress.membership",
|
||||||
|
"fields": {
|
||||||
|
"person": 1,
|
||||||
|
"price": 100,
|
||||||
|
"group": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 3,
|
||||||
|
"model": "m2m_through_regress.membership",
|
||||||
|
"fields": {
|
||||||
|
"person": 2,
|
||||||
|
"price": 100,
|
||||||
|
"group": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "m2m_through_regress.usermembership",
|
||||||
|
"fields": {
|
||||||
|
"price": 100,
|
||||||
|
"group": 1,
|
||||||
|
"user": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "m2m_through_regress.usermembership",
|
||||||
|
"fields": {
|
||||||
|
"price": 100,
|
||||||
|
"group": 2,
|
||||||
|
"user": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 3,
|
||||||
|
"model": "m2m_through_regress.usermembership",
|
||||||
|
"fields": {
|
||||||
|
"price": 100,
|
||||||
|
"group": 1,
|
||||||
|
"user": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "m2m_through_regress.person",
|
||||||
|
"fields": {
|
||||||
|
"name": "Bob"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "m2m_through_regress.person",
|
||||||
|
"fields": {
|
||||||
|
"name": "Jim"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "m2m_through_regress.group",
|
||||||
|
"fields": {
|
||||||
|
"name": "Rock"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "m2m_through_regress.group",
|
||||||
|
"fields": {
|
||||||
|
"name": "Roll"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check the XML serializer too, since it doesn't use the common implementation
|
||||||
|
>>> management.call_command('dumpdata', 'm2m_through_regress', format='xml', indent=2)
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<django-objects version="1.0">
|
||||||
|
<object pk="1" model="m2m_through_regress.membership">
|
||||||
|
<field to="m2m_through_regress.person" name="person" rel="ManyToOneRel">1</field>
|
||||||
|
<field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">1</field>
|
||||||
|
<field type="IntegerField" name="price">100</field>
|
||||||
|
</object>
|
||||||
|
<object pk="2" model="m2m_through_regress.membership">
|
||||||
|
<field to="m2m_through_regress.person" name="person" rel="ManyToOneRel">1</field>
|
||||||
|
<field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">2</field>
|
||||||
|
<field type="IntegerField" name="price">100</field>
|
||||||
|
</object>
|
||||||
|
<object pk="3" model="m2m_through_regress.membership">
|
||||||
|
<field to="m2m_through_regress.person" name="person" rel="ManyToOneRel">2</field>
|
||||||
|
<field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">1</field>
|
||||||
|
<field type="IntegerField" name="price">100</field>
|
||||||
|
</object>
|
||||||
|
<object pk="1" model="m2m_through_regress.usermembership">
|
||||||
|
<field to="auth.user" name="user" rel="ManyToOneRel">1</field>
|
||||||
|
<field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">1</field>
|
||||||
|
<field type="IntegerField" name="price">100</field>
|
||||||
|
</object>
|
||||||
|
<object pk="2" model="m2m_through_regress.usermembership">
|
||||||
|
<field to="auth.user" name="user" rel="ManyToOneRel">1</field>
|
||||||
|
<field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">2</field>
|
||||||
|
<field type="IntegerField" name="price">100</field>
|
||||||
|
</object>
|
||||||
|
<object pk="3" model="m2m_through_regress.usermembership">
|
||||||
|
<field to="auth.user" name="user" rel="ManyToOneRel">2</field>
|
||||||
|
<field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">1</field>
|
||||||
|
<field type="IntegerField" name="price">100</field>
|
||||||
|
</object>
|
||||||
|
<object pk="1" model="m2m_through_regress.person">
|
||||||
|
<field type="CharField" name="name">Bob</field>
|
||||||
|
</object>
|
||||||
|
<object pk="2" model="m2m_through_regress.person">
|
||||||
|
<field type="CharField" name="name">Jim</field>
|
||||||
|
</object>
|
||||||
|
<object pk="1" model="m2m_through_regress.group">
|
||||||
|
<field type="CharField" name="name">Rock</field>
|
||||||
|
</object>
|
||||||
|
<object pk="2" model="m2m_through_regress.group">
|
||||||
|
<field type="CharField" name="name">Roll</field>
|
||||||
|
</object>
|
||||||
|
</django-objects>
|
||||||
|
|
||||||
"""}
|
"""}
|
|
@ -132,6 +132,14 @@ class FKDataToField(models.Model):
|
||||||
class FKDataToO2O(models.Model):
|
class FKDataToO2O(models.Model):
|
||||||
data = models.ForeignKey(O2OData, null=True)
|
data = models.ForeignKey(O2OData, null=True)
|
||||||
|
|
||||||
|
class M2MIntermediateData(models.Model):
|
||||||
|
data = models.ManyToManyField(Anchor, null=True, through='Intermediate')
|
||||||
|
|
||||||
|
class Intermediate(models.Model):
|
||||||
|
left = models.ForeignKey(M2MIntermediateData)
|
||||||
|
right = models.ForeignKey(Anchor)
|
||||||
|
extra = models.CharField(max_length=30, blank=True, default="doesn't matter")
|
||||||
|
|
||||||
# The following test classes are for validating the
|
# The following test classes are for validating the
|
||||||
# deserialization of objects that use a user-defined
|
# deserialization of objects that use a user-defined
|
||||||
# field as the primary key.
|
# field as the primary key.
|
||||||
|
@ -243,3 +251,4 @@ class InheritBaseModel(BaseModel):
|
||||||
class ExplicitInheritBaseModel(BaseModel):
|
class ExplicitInheritBaseModel(BaseModel):
|
||||||
parent = models.OneToOneField(BaseModel)
|
parent = models.OneToOneField(BaseModel)
|
||||||
child_data = models.IntegerField()
|
child_data = models.IntegerField()
|
||||||
|
|
|
@ -54,6 +54,20 @@ def m2m_create(pk, klass, data):
|
||||||
instance.data = data
|
instance.data = data
|
||||||
return [instance]
|
return [instance]
|
||||||
|
|
||||||
|
def im2m_create(pk, klass, data):
|
||||||
|
instance = klass(id=pk)
|
||||||
|
models.Model.save_base(instance, raw=True)
|
||||||
|
return [instance]
|
||||||
|
|
||||||
|
def im_create(pk, klass, data):
|
||||||
|
instance = klass(id=pk)
|
||||||
|
setattr(instance, 'right_id', data['right'])
|
||||||
|
setattr(instance, 'left_id', data['left'])
|
||||||
|
if 'extra' in data:
|
||||||
|
setattr(instance, 'extra', data['extra'])
|
||||||
|
models.Model.save_base(instance, raw=True)
|
||||||
|
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
|
||||||
|
@ -99,6 +113,19 @@ def m2m_compare(testcase, pk, klass, data):
|
||||||
instance = klass.objects.get(id=pk)
|
instance = klass.objects.get(id=pk)
|
||||||
testcase.assertEqual(data, [obj.id for obj in instance.data.all()])
|
testcase.assertEqual(data, [obj.id for obj in instance.data.all()])
|
||||||
|
|
||||||
|
def im2m_compare(testcase, pk, klass, data):
|
||||||
|
instance = klass.objects.get(id=pk)
|
||||||
|
#actually nothing else to check, the instance just should exist
|
||||||
|
|
||||||
|
def im_compare(testcase, pk, klass, data):
|
||||||
|
instance = klass.objects.get(id=pk)
|
||||||
|
testcase.assertEqual(data['left'], instance.left_id)
|
||||||
|
testcase.assertEqual(data['right'], instance.right_id)
|
||||||
|
if 'extra' in data:
|
||||||
|
testcase.assertEqual(data['extra'], instance.extra)
|
||||||
|
else:
|
||||||
|
testcase.assertEqual("doesn't matter", instance.extra)
|
||||||
|
|
||||||
def o2o_compare(testcase, pk, klass, data):
|
def o2o_compare(testcase, pk, klass, data):
|
||||||
instance = klass.objects.get(data=data)
|
instance = klass.objects.get(data=data)
|
||||||
testcase.assertEqual(data, instance.data_id)
|
testcase.assertEqual(data, instance.data_id)
|
||||||
|
@ -119,6 +146,8 @@ data_obj = (data_create, data_compare)
|
||||||
generic_obj = (generic_create, generic_compare)
|
generic_obj = (generic_create, generic_compare)
|
||||||
fk_obj = (fk_create, fk_compare)
|
fk_obj = (fk_create, fk_compare)
|
||||||
m2m_obj = (m2m_create, m2m_compare)
|
m2m_obj = (m2m_create, m2m_compare)
|
||||||
|
im2m_obj = (im2m_create, im2m_compare)
|
||||||
|
im_obj = (im_create, im_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)
|
inherited_obj = (inherited_create, inherited_compare)
|
||||||
|
@ -232,6 +261,20 @@ The end."""),
|
||||||
|
|
||||||
(fk_obj, 460, FKDataToO2O, 300),
|
(fk_obj, 460, FKDataToO2O, 300),
|
||||||
|
|
||||||
|
(im2m_obj, 470, M2MIntermediateData, None),
|
||||||
|
|
||||||
|
#testing post- and prereferences and extra fields
|
||||||
|
(im_obj, 480, Intermediate, {'right': 300, 'left': 470}),
|
||||||
|
(im_obj, 481, Intermediate, {'right': 300, 'left': 490}),
|
||||||
|
(im_obj, 482, Intermediate, {'right': 500, 'left': 470}),
|
||||||
|
(im_obj, 483, Intermediate, {'right': 500, 'left': 490}),
|
||||||
|
(im_obj, 484, Intermediate, {'right': 300, 'left': 470, 'extra': "extra"}),
|
||||||
|
(im_obj, 485, Intermediate, {'right': 300, 'left': 490, 'extra': "extra"}),
|
||||||
|
(im_obj, 486, Intermediate, {'right': 500, 'left': 470, 'extra': "extra"}),
|
||||||
|
(im_obj, 487, Intermediate, {'right': 500, 'left': 490, 'extra': "extra"}),
|
||||||
|
|
||||||
|
(im2m_obj, 490, M2MIntermediateData, []),
|
||||||
|
|
||||||
(data_obj, 500, Anchor, "Anchor 3"),
|
(data_obj, 500, Anchor, "Anchor 3"),
|
||||||
(data_obj, 501, Anchor, "Anchor 4"),
|
(data_obj, 501, Anchor, "Anchor 4"),
|
||||||
(data_obj, 502, UniqueAnchor, "UAnchor 2"),
|
(data_obj, 502, UniqueAnchor, "UAnchor 2"),
|
||||||
|
|
Loading…
Reference in New Issue