From 9fecb86a5221982ea753270b7d2b4c6487f79941 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 21 Dec 2014 14:24:15 +0100 Subject: [PATCH] Fixed #17946 -- Fixed deserialization of self-referencing M2M fields Thanks Philip Mountifield for the report and excellent analysis, and Simon Charette for the review. --- django/core/serializers/python.py | 14 +++++++------- tests/fixtures_regress/fixtures/m2mtoself.json | 1 + tests/fixtures_regress/models.py | 4 ++++ tests/fixtures_regress/tests.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures_regress/fixtures/m2mtoself.json diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index db4e11452e..b4712c76be 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -9,7 +9,7 @@ from django.apps import apps from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS -from django.utils.encoding import smart_text, is_protected_type +from django.utils.encoding import force_text, is_protected_type from django.utils import six @@ -36,11 +36,11 @@ class Serializer(base.Serializer): def get_dump_object(self, obj): data = { - "model": smart_text(obj._meta), + "model": force_text(obj._meta), "fields": self._current, } if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'): - data["pk"] = smart_text(obj._get_pk_val(), strings_only=True) + data["pk"] = force_text(obj._get_pk_val(), strings_only=True) return data @@ -70,7 +70,7 @@ class Serializer(base.Serializer): if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'): m2m_value = lambda value: value.natural_key() else: - m2m_value = lambda value: smart_text(value._get_pk_val(), strings_only=True) + m2m_value = lambda value: force_text(value._get_pk_val(), strings_only=True) self._current[field.name] = [m2m_value(related) for related in getattr(obj, field.name).iterator()] @@ -111,7 +111,7 @@ def Deserializer(object_list, **options): continue if isinstance(field_value, str): - field_value = smart_text( + field_value = force_text( field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True ) @@ -124,9 +124,9 @@ def Deserializer(object_list, **options): if hasattr(value, '__iter__') and not isinstance(value, six.text_type): return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk else: - return smart_text(field.rel.to._meta.pk.to_python(value)) + return force_text(field.rel.to._meta.pk.to_python(value), strings_only=True) else: - m2m_convert = lambda v: smart_text(field.rel.to._meta.pk.to_python(v)) + m2m_convert = lambda v: force_text(field.rel.to._meta.pk.to_python(v), strings_only=True) m2m_data[field.name] = [m2m_convert(pk) for pk in field_value] # Handle FK fields diff --git a/tests/fixtures_regress/fixtures/m2mtoself.json b/tests/fixtures_regress/fixtures/m2mtoself.json new file mode 100644 index 0000000000..b904ba36e0 --- /dev/null +++ b/tests/fixtures_regress/fixtures/m2mtoself.json @@ -0,0 +1 @@ +[{"fields": {"parent": [1]}, "model": "fixtures_regress.m2mtoself", "pk": 1}] diff --git a/tests/fixtures_regress/models.py b/tests/fixtures_regress/models.py index f75b1e02d0..cd381e92bd 100644 --- a/tests/fixtures_regress/models.py +++ b/tests/fixtures_regress/models.py @@ -248,6 +248,10 @@ class Thingy(models.Model): name = models.CharField(max_length=255) +class M2MToSelf(models.Model): + parent = models.ManyToManyField("self", blank=True, null=True) + + @python_2_unicode_compatible class BaseNKModel(models.Model): """ diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index 89b8fb17ef..a91935af8f 100644 --- a/tests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -487,6 +487,16 @@ class TestFixtures(TestCase): verbosity=0, ) + def test_loaddata_with_m2m_to_self(self): + """ + Regression test for ticket #17946. + """ + management.call_command( + 'loaddata', + 'm2mtoself.json', + verbosity=0, + ) + @override_settings(FIXTURE_DIRS=[os.path.join(_cur_dir, 'fixtures_1'), os.path.join(_cur_dir, 'fixtures_1')]) def test_fixture_dirs_with_duplicates(self):