diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 322ab1a13..ba6870bcf 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 744823c48..7e04e7e47 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -21,6 +21,15 @@ from rest_framework.reverse import reverse from rest_framework.utils import html +def method_overridden(method_name, klass, instance): + """ + Determine if a method has been overridden. + """ + method = getattr(klass, method_name) + default_method = getattr(method, '__func__', method) # Python 3 compat + return default_method is not getattr(instance, method_name).__func__ + + class Hyperlink(six.text_type): """ A string like object that additionally has an associated name. @@ -65,10 +74,12 @@ 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`.' - ) + + if not method_overridden('get_queryset', RelatedField, self): + assert self.queryset is not None or kwargs.get('read_only', None), ( + 'Relational field must provide a `queryset` argument, ' + 'override `get_queryset`, 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`.' diff --git a/tests/test_relations.py b/tests/test_relations.py index 9469cd068..87dc28451 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):