Skip to content

Commit

Permalink
Fixed #8134 -- Corrected serialization of m2m fields with intermediat…
Browse files Browse the repository at this point in the history
…e 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
  • Loading branch information
freakboy3742 committed Aug 12, 2008
1 parent dd970bf commit 63ea576
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 9 deletions.
5 changes: 3 additions & 2 deletions django/core/serializers/python.py
Expand Up @@ -49,8 +49,9 @@ def handle_fk_field(self, obj, field):
self._current[field.name] = smart_unicode(related, strings_only=True)

def handle_m2m_field(self, obj, field):
self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
for related in getattr(obj, field.name).iterator()]
if field.creates_table:
self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
for related in getattr(obj, field.name).iterator()]

def getvalue(self):
return self.objects
Expand Down
9 changes: 5 additions & 4 deletions django/core/serializers/xml_serializer.py
Expand Up @@ -100,10 +100,11 @@ def handle_m2m_field(self, obj, field):
serialized as references to the object's PK (i.e. the related *data*
is not dumped, just the relation).
"""
self._start_relational_field(field)
for relobj in getattr(obj, field.name).iterator():
self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
self.xml.endElement("field")
if field.creates_table:
self._start_relational_field(field)
for relobj in getattr(obj, field.name).iterator():
self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
self.xml.endElement("field")

def _start_relational_field(self, field):
"""
Expand Down
144 changes: 141 additions & 3 deletions tests/regressiontests/m2m_through_regress/models.py
@@ -1,20 +1,21 @@
from django.db import models
from datetime import datetime
from django.contrib.auth.models import User
from django.core import management
from django.db import models

# Forward declared intermediate model
class Membership(models.Model):
person = models.ForeignKey('Person')
group = models.ForeignKey('Group')
date_joined = models.DateTimeField(default=datetime.now)
price = models.IntegerField(default=100)

def __unicode__(self):
return "%s is a member of %s" % (self.person.name, self.group.name)

class UserMembership(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey('Group')
date_joined = models.DateTimeField(default=datetime.now)
price = models.IntegerField(default=100)

def __unicode__(self):
return "%s is a user and member of %s" % (self.user.username, self.group.name)
Expand Down Expand Up @@ -99,4 +100,141 @@ def __unicode__(self):
>>> roll.user_members.all()
[<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>
"""}
9 changes: 9 additions & 0 deletions tests/regressiontests/serializers_regress/models.py
Expand Up @@ -132,6 +132,14 @@ class FKDataToField(models.Model):
class FKDataToO2O(models.Model):
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
# deserialization of objects that use a user-defined
# field as the primary key.
Expand Down Expand Up @@ -243,3 +251,4 @@ class InheritBaseModel(BaseModel):
class ExplicitInheritBaseModel(BaseModel):
parent = models.OneToOneField(BaseModel)
child_data = models.IntegerField()

43 changes: 43 additions & 0 deletions tests/regressiontests/serializers_regress/tests.py
Expand Up @@ -54,6 +54,20 @@ def m2m_create(pk, klass, data):
instance.data = data
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):
instance = klass()
instance.data_id = data
Expand Down Expand Up @@ -99,6 +113,19 @@ def m2m_compare(testcase, pk, klass, data):
instance = klass.objects.get(id=pk)
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):
instance = klass.objects.get(data=data)
testcase.assertEqual(data, instance.data_id)
Expand All @@ -119,6 +146,8 @@ def inherited_compare(testcase, pk, klass, data):
generic_obj = (generic_create, generic_compare)
fk_obj = (fk_create, fk_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)
pk_obj = (pk_create, pk_compare)
inherited_obj = (inherited_create, inherited_compare)
Expand Down Expand Up @@ -231,6 +260,20 @@ def inherited_compare(testcase, pk, klass, data):
(fk_obj, 452, FKDataToField, None),

(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, 501, Anchor, "Anchor 4"),
Expand Down

0 comments on commit 63ea576

Please sign in to comment.