From f2452936e9a33ce6eb95b11bd89b34fc6d2991a0 Mon Sep 17 00:00:00 2001 From: Ryan Hiebert Date: Thu, 5 Nov 2015 13:07:40 -0600 Subject: [PATCH] Allow no queryset when get_queryset overridden The user may wish to provide a dynamic queryset on a `RelatedField` based on the `context`. The way to do that is to create a subclass of `RelatedField` (or a child) and override the `get_queryset` method. However, this is undocumented, and instantiating that field without a `queryset` argument (because it's not needed) will raise an assertion error. Document `.get_queryset(self)` as an official part of the API of `RelatedField`, and don't enforce the use of `queryset` when `get_queryset` is overridden. --- docs/api-guide/relations.md | 2 ++ rest_framework/relations.py | 8 ++++---- tests/test_relations.py | 8 ++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 51e5f78c4..d5a7bfc7f 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -330,6 +330,8 @@ To implement a custom relational field, you should override `RelatedField`, and If you want to implement a read-write relational field, you must also implement the `.to_internal_value(self, data)` method. +To provide a dynamic queryset based on the `context`, you can also override `.get_queryset(self)` instead of specifying `.queryset` on the class or when initializing the field. + ## Example For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration. diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 40261f3f3..74e0f3e03 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -62,10 +62,6 @@ class RelatedField(Field): self.queryset = kwargs.pop('queryset', self.queryset) self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff) self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text) - assert self.queryset is not None or kwargs.get('read_only', None), ( - 'Relational field must provide a `queryset` argument, ' - 'or set read_only=`True`.' - ) assert not (self.queryset is not None and kwargs.get('read_only', None)), ( 'Relational fields should not provide a `queryset` argument, ' 'when setting read_only=`True`.' @@ -112,6 +108,10 @@ class RelatedField(Field): def get_queryset(self): queryset = self.queryset + assert queryset is not None, ( + 'Relational field must provide a `queryset` argument, ' + 'or set read_only=`True`.' + ) if isinstance(queryset, (QuerySet, Manager)): # Ensure queryset is re-evaluated whenever used. # Note that actually a `Manager` class may also be used as the diff --git a/tests/test_relations.py b/tests/test_relations.py index fd37e63e3..9703c3b9d 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -176,6 +176,14 @@ class TestSlugRelatedField(APISimpleTestCase): representation = self.field.to_representation(self.instance) assert representation == self.instance.name + def test_no_queryset_init(self): + class NoQuerySetSlugRelatedField(serializers.SlugRelatedField): + def get_queryset(this): + return self.queryset + + field = NoQuerySetSlugRelatedField(slug_field='name') + field.to_internal_value(self.instance.name) + class TestManyRelatedField(APISimpleTestCase): def setUp(self):