From caad2ed6685563466520299fc4dcdeb5e2e296d6 Mon Sep 17 00:00:00 2001 From: pscottdevos Date: Wed, 20 Jul 2016 14:11:51 -0500 Subject: [PATCH 1/5] improved lookup of "to_field" when building relational field Previously could fail when serializing a model that: 1. was derived (subclassed) from a model 2. was itself referenced via a one-to-one relationship by a third model 3. had a serializer that listed the referring model in its Meta.fields Now we recursively check up inheritance chain for the referenced field --- rest_framework/serializers.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8c39202f4..f76d5932d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1157,9 +1157,34 @@ class ModelSerializer(Serializer): field_kwargs = get_relation_kwargs(field_name, relation_info) to_field = field_kwargs.pop('to_field', None) - if to_field and not relation_info.related_model._meta.get_field(to_field).primary_key: - field_kwargs['slug_field'] = to_field - field_class = self.serializer_related_to_field + if to_field: + def get_related_field(related_model, to_field): + '''Returns the primary key of the field defined by to_field + on the model passed in''' + from django.core.exceptions import FieldDoesNotExist + try: + return related_model._meta.get_field(to_field) + except FieldDoesNotExist as e: + for field in related_model._meta.fields: + if field.related_model: + try: + new_field = get_related_field( + field.related_model, to_field) + return new_field + except FieldDoesNotExist as e: + continue + raise FieldDoesNotExist( + '%s has not field named %r' % (related_model, to_field)) + #try: + # related_pk = (relation_info.related_model._meta + # .get_field(to_field).primary_key) + #except FieldDoesNotExist as e: + # import ipdb; ipdb.set_trace() + pk = (get_related_field(relation_info.related_model, to_field) + .primary_key) + if not pk: + field_kwargs['slug_field'] = to_field + field_class = self.serializer_related_to_field # `view_name` is only valid for hyperlinked relationships. if not issubclass(field_class, HyperlinkedRelatedField): From 8ac0c0cbea359004d0f22ddfe3c0db17271e09dd Mon Sep 17 00:00:00 2001 From: pscottdevos Date: Wed, 20 Jul 2016 15:29:03 -0500 Subject: [PATCH 2/5] Moved tests to exhibit the inheritance problem to separate file --- tests/test_onetoone_with_inheritance.py | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/test_onetoone_with_inheritance.py diff --git a/tests/test_onetoone_with_inheritance.py b/tests/test_onetoone_with_inheritance.py new file mode 100644 index 000000000..51fe194ef --- /dev/null +++ b/tests/test_onetoone_with_inheritance.py @@ -0,0 +1,56 @@ +from __future__ import unicode_literals + +from django.db import models +from django.test import TestCase + +from rest_framework import serializers +from tests.models import RESTFrameworkModel + + +# Models +from tests.test_multitable_inheritance import ChildModel, DerivedModelSerializer + + +class ChildAssociatedModel(RESTFrameworkModel): + child_model = models.OneToOneField(ChildModel) + child_name = models.CharField(max_length=100) + + +# Serializers +class DerivedModelSerializer(serializers.ModelSerializer): + class Meta: + model = ChildModel + fields = ['id', 'name1', 'name2', 'childassociatedmodel'] + + +class ChildAssociatedModelSerializer(serializers.ModelSerializer): + + class Meta: + model = ChildAssociatedModel + fields = ['id', 'child_name'] + + +# Tests +class InheritedModelSerializationTests(TestCase): + + def test_multitable_inherited_model_fields_as_expected(self): + """ + Assert that the parent pointer field is not included in the fields + serialized fields + """ + child = ChildModel(name1='parent name', name2='child name') + serializer = DerivedModelSerializer(child) + self.assertEqual(set(serializer.data.keys()), + set(['name1', 'name2', 'id', 'childassociatedmodel'])) + + def test_data_is_valid_without_parent_ptr(self): + """ + Assert that the pointer to the parent table is not a required field + for input data + """ + data = { + 'name1': 'parent name', + 'name2': 'child name', + } + serializer = DerivedModelSerializer(data=data) + self.assertEqual(serializer.is_valid(), True) From 61dbf89bd548c4bd4f23a7fbc686616c4e7029f0 Mon Sep 17 00:00:00 2001 From: pscottdevos Date: Wed, 20 Jul 2016 15:31:42 -0500 Subject: [PATCH 3/5] Update unit tests to exhibit problem Exhibits the problem that occurs when a model has a one-to-one relationship with another model that is itself derived (subclassed) from a third model. --- tests/test_onetoone_with_inheritance.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/test_onetoone_with_inheritance.py b/tests/test_onetoone_with_inheritance.py index 51fe194ef..d58ffc415 100644 --- a/tests/test_onetoone_with_inheritance.py +++ b/tests/test_onetoone_with_inheritance.py @@ -42,15 +42,3 @@ class InheritedModelSerializationTests(TestCase): serializer = DerivedModelSerializer(child) self.assertEqual(set(serializer.data.keys()), set(['name1', 'name2', 'id', 'childassociatedmodel'])) - - def test_data_is_valid_without_parent_ptr(self): - """ - Assert that the pointer to the parent table is not a required field - for input data - """ - data = { - 'name1': 'parent name', - 'name2': 'child name', - } - serializer = DerivedModelSerializer(data=data) - self.assertEqual(serializer.is_valid(), True) From f9d46d60d0c0292cc27e7ac3558c49725dcad722 Mon Sep 17 00:00:00 2001 From: pscottdevos Date: Wed, 20 Jul 2016 15:43:36 -0500 Subject: [PATCH 4/5] satisfy flake8 lintig --- rest_framework/serializers.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index f76d5932d..85b1f81f1 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1164,24 +1164,19 @@ class ModelSerializer(Serializer): from django.core.exceptions import FieldDoesNotExist try: return related_model._meta.get_field(to_field) - except FieldDoesNotExist as e: + except FieldDoesNotExist: for field in related_model._meta.fields: if field.related_model: try: new_field = get_related_field( field.related_model, to_field) return new_field - except FieldDoesNotExist as e: + except FieldDoesNotExist: continue raise FieldDoesNotExist( '%s has not field named %r' % (related_model, to_field)) - #try: - # related_pk = (relation_info.related_model._meta - # .get_field(to_field).primary_key) - #except FieldDoesNotExist as e: - # import ipdb; ipdb.set_trace() pk = (get_related_field(relation_info.related_model, to_field) - .primary_key) + .primary_key) if not pk: field_kwargs['slug_field'] = to_field field_class = self.serializer_related_to_field From 7a5c3153e4c366046616deddbaa5d94ec1f74df4 Mon Sep 17 00:00:00 2001 From: pscottdevos Date: Thu, 21 Jul 2016 07:43:15 -0500 Subject: [PATCH 5/5] Remove unneeded import --- tests/test_onetoone_with_inheritance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_onetoone_with_inheritance.py b/tests/test_onetoone_with_inheritance.py index d58ffc415..3b9e24dbc 100644 --- a/tests/test_onetoone_with_inheritance.py +++ b/tests/test_onetoone_with_inheritance.py @@ -8,7 +8,7 @@ from tests.models import RESTFrameworkModel # Models -from tests.test_multitable_inheritance import ChildModel, DerivedModelSerializer +from tests.test_multitable_inheritance import ChildModel class ChildAssociatedModel(RESTFrameworkModel):