diff --git a/dynamic_rest/filters.py b/dynamic_rest/filters.py index 91855d05..74eea336 100644 --- a/dynamic_rest/filters.py +++ b/dynamic_rest/filters.py @@ -518,6 +518,18 @@ def _build_queryset( requirements ) + # Implicit requirements (i.e. via `requires`) can potentially + # include fields that haven't been explicitly included. + # Such fields would not be in `fields`, so they need to be added. + implicitly_included = set(requirements.keys()) - set(fields.keys()) + if implicitly_included: + all_fields = serializer.get_all_fields() + fields.update({ + field: all_fields[field] + for field in implicitly_included + if field in all_fields + }) + if filters is None: filters = self._get_requested_filters() diff --git a/tests/serializers.py b/tests/serializers.py index b9d0aaca..21045c52 100644 --- a/tests/serializers.py +++ b/tests/serializers.py @@ -63,7 +63,8 @@ class Meta: 'UserSerializer', source='user_set', many=True, - deferred=True) + deferred=True + ) user_count = CountField('users', required=False, deferred=True) address = DynamicField(source='blob', required=False, deferred=True) cats = DynamicRelationField( @@ -73,6 +74,9 @@ class Meta: bad_cats = DynamicRelationField( 'CatSerializer', source='annoying_cats', many=True, deferred=True) + def filter_queryset(self, query): + return query.exclude(name='Atlantis') + class PermissionSerializer(DynamicModelSerializer): diff --git a/tests/test_api.py b/tests/test_api.py index 683fef14..bb7e1097 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -827,6 +827,51 @@ def test_get_with_request_filters_and_requires(self): }] }, json.loads(response.content.decode('utf-8'))) + def test_implicit_vs_explicit_prefetch(self): + """ + LocationSerializer has a built-in filter to hide Atlantis. + UserSerializer can explicitly include Location, and it can also + implicitly require Location through the `number_of_cats` field. + This test ensures that LocationSerializer.filter_queryset() is + being respected regardless of whether `User.location` is being + included implicitly or explicitly. + """ + atlantis = Location.objects.create(name='Atlantis') + atlantian = User.objects.create( + name='Atlantian', + last_name='Human', + location=atlantis + ) + Cat.objects.create( + name='Gato', + home=atlantis, + backup_home=self.fixture.locations[0], + ) + + url = ( + '/users/%s/?' + 'include[]=number_of_cats&' + 'include[]=location.' + ) % atlantian.pk + response1 = self._get_json(url) + + url = ( + '/users/%s/?' + 'include[]=number_of_cats&' + 'exclude[]=location' + ) % atlantian.pk + response2 = self._get_json(url) + + # Atlantis is hidden, therefore its cats are also hidden + self.assertEqual( + response1['user']['number_of_cats'], + 0 + ) + self.assertEqual( + response1['user']['number_of_cats'], + response2['user']['number_of_cats'] + ) + def test_boolean_filters_on_boolean_field(self): # create one dead user User.objects.create(name='Dead', last_name='Mort', is_dead=True)