Fixed #17946 -- Fixed deserialization of self-referencing M2M fields
Thanks Philip Mountifield for the report and excellent analysis, and Simon Charette for the review.
This commit is contained in:
parent
227a4c48be
commit
9fecb86a52
|
@ -9,7 +9,7 @@ from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.serializers import base
|
from django.core.serializers import base
|
||||||
from django.db import models, DEFAULT_DB_ALIAS
|
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
|
from django.utils import six
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,11 +36,11 @@ class Serializer(base.Serializer):
|
||||||
|
|
||||||
def get_dump_object(self, obj):
|
def get_dump_object(self, obj):
|
||||||
data = {
|
data = {
|
||||||
"model": smart_text(obj._meta),
|
"model": force_text(obj._meta),
|
||||||
"fields": self._current,
|
"fields": self._current,
|
||||||
}
|
}
|
||||||
if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
|
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
|
return data
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ class Serializer(base.Serializer):
|
||||||
if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'):
|
if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'):
|
||||||
m2m_value = lambda value: value.natural_key()
|
m2m_value = lambda value: value.natural_key()
|
||||||
else:
|
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)
|
self._current[field.name] = [m2m_value(related)
|
||||||
for related in getattr(obj, field.name).iterator()]
|
for related in getattr(obj, field.name).iterator()]
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ def Deserializer(object_list, **options):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(field_value, str):
|
if isinstance(field_value, str):
|
||||||
field_value = smart_text(
|
field_value = force_text(
|
||||||
field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True
|
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):
|
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
|
return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk
|
||||||
else:
|
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:
|
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]
|
m2m_data[field.name] = [m2m_convert(pk) for pk in field_value]
|
||||||
|
|
||||||
# Handle FK fields
|
# Handle FK fields
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
[{"fields": {"parent": [1]}, "model": "fixtures_regress.m2mtoself", "pk": 1}]
|
|
@ -248,6 +248,10 @@ class Thingy(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
|
||||||
|
class M2MToSelf(models.Model):
|
||||||
|
parent = models.ManyToManyField("self", blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class BaseNKModel(models.Model):
|
class BaseNKModel(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -487,6 +487,16 @@ class TestFixtures(TestCase):
|
||||||
verbosity=0,
|
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'),
|
@override_settings(FIXTURE_DIRS=[os.path.join(_cur_dir, 'fixtures_1'),
|
||||||
os.path.join(_cur_dir, 'fixtures_1')])
|
os.path.join(_cur_dir, 'fixtures_1')])
|
||||||
def test_fixture_dirs_with_duplicates(self):
|
def test_fixture_dirs_with_duplicates(self):
|
||||||
|
|
Loading…
Reference in New Issue