diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index cedab06be9c..c129c068df1 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -49,8 +49,9 @@ class Serializer(base.Serializer): 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 diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 667faf7c5ec..04498db00c1 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -100,10 +100,11 @@ class Serializer(base.Serializer): 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): """ diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py index bb9073a1728..eed0cc1b5f6 100644 --- a/tests/regressiontests/m2m_through_regress/models.py +++ b/tests/regressiontests/m2m_through_regress/models.py @@ -1,12 +1,13 @@ -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) @@ -14,7 +15,7 @@ class Membership(models.Model): 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) @@ -99,4 +100,141 @@ AttributeError: Cannot use create() on a ManyToManyField which specifies an inte >>> roll.user_members.all() [] +# 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) + + + + 1 + 1 + 100 + + + 1 + 2 + 100 + + + 2 + 1 + 100 + + + 1 + 1 + 100 + + + 1 + 2 + 100 + + + 2 + 1 + 100 + + + Bob + + + Jim + + + Rock + + + Roll + + + """} \ No newline at end of file diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py index 4e2fbb1ee2b..d2c06234fb3 100644 --- a/tests/regressiontests/serializers_regress/models.py +++ b/tests/regressiontests/serializers_regress/models.py @@ -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. @@ -243,3 +251,4 @@ class InheritBaseModel(BaseModel): class ExplicitInheritBaseModel(BaseModel): parent = models.OneToOneField(BaseModel) child_data = models.IntegerField() + \ No newline at end of file diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index 7a38af4cf97..e49ab9de5cc 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -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 @@ -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) @@ -119,6 +146,8 @@ data_obj = (data_create, data_compare) 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) @@ -231,6 +260,20 @@ The end."""), (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"),