From bd7925c181f52e31891198d8226c0daa9df75aaa Mon Sep 17 00:00:00 2001 From: Rob Golding Date: Sun, 28 Jan 2018 20:55:13 +0000 Subject: [PATCH 1/4] Give `ForeignKeySource` a custom manager This change allows the tests to ensure that both forward and reverse foreign key serialization works when the model has a custom manager. --- tests/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/models.py b/tests/models.py index e5d49a0a5..40f69031f 100644 --- a/tests/models.py +++ b/tests/models.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals import uuid from django.db import models +from django.db.models.manager import BaseManager +from django.db.models.query import QuerySet from django.utils.translation import ugettext_lazy as _ @@ -45,12 +47,18 @@ class UUIDForeignKeyTarget(RESTFrameworkModel): name = models.CharField(max_length=100) +class ForeignKeySourceManager(BaseManager.from_queryset(QuerySet)): + pass + + class ForeignKeySource(RESTFrameworkModel): name = models.CharField(max_length=100) target = models.ForeignKey(ForeignKeyTarget, related_name='sources', help_text='Target', verbose_name='Target', on_delete=models.CASCADE) + objects = ForeignKeySourceManager() + # Nullable ForeignKey class NullableForeignKeySource(RESTFrameworkModel): From c36e2eabe9e9984e56135dda1c3cac7109e285ca Mon Sep 17 00:00:00 2001 From: Rob Golding Date: Sun, 28 Jan 2018 20:58:08 +0000 Subject: [PATCH 2/4] Add `ForeignKeyTargetWithSourcesSerializer` to relations/pk tests This serializer nests the reverse `sources` relation, to test that the reverse relation is iterated over correctly. --- tests/test_relations_pk.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 3317d6251..7cba9d42d 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -26,16 +26,24 @@ class ManyToManySourceSerializer(serializers.ModelSerializer): # ForeignKey +class ForeignKeySourceSerializer(serializers.ModelSerializer): + class Meta: + model = ForeignKeySource + fields = ('id', 'name', 'target') + + class ForeignKeyTargetSerializer(serializers.ModelSerializer): class Meta: model = ForeignKeyTarget fields = ('id', 'name', 'sources') -class ForeignKeySourceSerializer(serializers.ModelSerializer): +class ForeignKeyTargetWithSourcesSerializer(serializers.ModelSerializer): + sources = ForeignKeySourceSerializer(many=True) + class Meta: - model = ForeignKeySource - fields = ('id', 'name', 'target') + model = ForeignKeyTarget + fields = ('id', 'name', 'sources') # Nullable ForeignKey From c1cc3ada7b5790deb05f3169dd5d7147e2efd471 Mon Sep 17 00:00:00 2001 From: Rob Golding Date: Sun, 28 Jan 2018 21:01:15 +0000 Subject: [PATCH 3/4] Use `ForeignKeyTargetWithSourcesSerializer` in relations/pk tests When checking the expected data, we now must ensure that the nested models are serialized correctly also. --- tests/test_relations_pk.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 7cba9d42d..5eb031c36 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -227,9 +227,13 @@ class PKForeignKeyTests(TestCase): def test_reverse_foreign_key_retrieve(self): queryset = ForeignKeyTarget.objects.all() - serializer = ForeignKeyTargetSerializer(queryset, many=True) + serializer = ForeignKeyTargetWithSourcesSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, + {'id': 1, 'name': 'target-1', 'sources': [ + {'id': 1, 'name': 'source-1', 'target': 1}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': 1}, + ]}, {'id': 2, 'name': 'target-2', 'sources': []}, ] with self.assertNumQueries(3): @@ -237,7 +241,7 @@ class PKForeignKeyTests(TestCase): def test_reverse_foreign_key_retrieve_prefetch_related(self): queryset = ForeignKeyTarget.objects.all().prefetch_related('sources') - serializer = ForeignKeyTargetSerializer(queryset, many=True) + serializer = ForeignKeyTargetWithSourcesSerializer(queryset, many=True) with self.assertNumQueries(2): serializer.data From 130b83623e346db5148bab98dd5787a893e40e18 Mon Sep 17 00:00:00 2001 From: Rob Golding Date: Sun, 28 Jan 2018 21:02:03 +0000 Subject: [PATCH 4/4] Fix `TypeError` bug in `ListSerializer.to_representation` The `iterable` can potentially be a subclass of `BaseManager` (and not necessarily `Manager`), which leads to a `TypeError` as the `isinstance` check fails (so a queryset is not obtained before iteration is attempted). --- rest_framework/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 33da0bad0..b28c06586 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -20,7 +20,7 @@ from collections import Mapping, OrderedDict from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ImproperlyConfigured from django.db import models -from django.db.models import DurationField as ModelDurationField +from django.db.models import manager, DurationField as ModelDurationField from django.db.models.fields import Field as DjangoModelField from django.db.models.fields import FieldDoesNotExist from django.utils import six, timezone @@ -654,7 +654,7 @@ class ListSerializer(BaseSerializer): """ # Dealing with nested relationships, data can be a Manager, # so, first get a queryset from the Manager if needed - iterable = data.all() if isinstance(data, models.Manager) else data + iterable = data.all() if isinstance(data, manager.BaseManager) else data return [ self.child.to_representation(item) for item in iterable