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.
This commit is contained in:
Ryan Hiebert 2015-11-05 13:07:40 -06:00
parent 2704036ad5
commit f2452936e9
3 changed files with 14 additions and 4 deletions

View File

@ -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. 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 ## Example
For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration. For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration.

View File

@ -62,10 +62,6 @@ class RelatedField(Field):
self.queryset = kwargs.pop('queryset', self.queryset) self.queryset = kwargs.pop('queryset', self.queryset)
self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff) self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff)
self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text) 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)), ( assert not (self.queryset is not None and kwargs.get('read_only', None)), (
'Relational fields should not provide a `queryset` argument, ' 'Relational fields should not provide a `queryset` argument, '
'when setting read_only=`True`.' 'when setting read_only=`True`.'
@ -112,6 +108,10 @@ class RelatedField(Field):
def get_queryset(self): def get_queryset(self):
queryset = self.queryset 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)): if isinstance(queryset, (QuerySet, Manager)):
# Ensure queryset is re-evaluated whenever used. # Ensure queryset is re-evaluated whenever used.
# Note that actually a `Manager` class may also be used as the # Note that actually a `Manager` class may also be used as the

View File

@ -176,6 +176,14 @@ class TestSlugRelatedField(APISimpleTestCase):
representation = self.field.to_representation(self.instance) representation = self.field.to_representation(self.instance)
assert representation == self.instance.name 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): class TestManyRelatedField(APISimpleTestCase):
def setUp(self): def setUp(self):