Ordering filter bug with model property serializer field (#7609)

* Add failing tests for ordering filter with model property

* Fix get_default_valid_fields of OrderingFilter

* Filter model properties in get_default_valid_fields of OrderingFilter
This commit is contained in:
Ömer Faruk Abacı 2021-03-16 15:53:39 +03:00 committed by GitHub
parent b256c46cb1
commit ce1568322a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 1 deletions

View File

@ -226,10 +226,20 @@ class OrderingFilter(BaseFilterBackend):
) )
raise ImproperlyConfigured(msg % self.__class__.__name__) raise ImproperlyConfigured(msg % self.__class__.__name__)
model_class = queryset.model
model_property_names = [
# 'pk' is a property added in Django's Model class, however it is valid for ordering.
attr for attr in dir(model_class) if isinstance(getattr(model_class, attr), property) and attr != 'pk'
]
return [ return [
(field.source.replace('.', '__') or field_name, field.label) (field.source.replace('.', '__') or field_name, field.label)
for field_name, field in serializer_class(context=context).fields.items() for field_name, field in serializer_class(context=context).fields.items()
if not getattr(field, 'write_only', False) and not field.source == '*' if (
not getattr(field, 'write_only', False) and
not field.source == '*' and
field.source not in model_property_names
)
] ]
def get_valid_fields(self, queryset, view, context={}): def get_valid_fields(self, queryset, view, context={}):

View File

@ -424,6 +424,10 @@ class OrderingFilterModel(models.Model):
title = models.CharField(max_length=20, verbose_name='verbose title') title = models.CharField(max_length=20, verbose_name='verbose title')
text = models.CharField(max_length=100) text = models.CharField(max_length=100)
@property
def description(self):
return self.title + ": " + self.text
class OrderingFilterRelatedModel(models.Model): class OrderingFilterRelatedModel(models.Model):
related_object = models.ForeignKey(OrderingFilterModel, related_name="relateds", on_delete=models.CASCADE) related_object = models.ForeignKey(OrderingFilterModel, related_name="relateds", on_delete=models.CASCADE)
@ -436,6 +440,17 @@ class OrderingFilterSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
class OrderingFilterSerializerWithModelProperty(serializers.ModelSerializer):
class Meta:
model = OrderingFilterModel
fields = (
"id",
"title",
"text",
"description"
)
class OrderingDottedRelatedSerializer(serializers.ModelSerializer): class OrderingDottedRelatedSerializer(serializers.ModelSerializer):
related_text = serializers.CharField(source='related_object.text') related_text = serializers.CharField(source='related_object.text')
related_title = serializers.CharField(source='related_object.title') related_title = serializers.CharField(source='related_object.title')
@ -551,6 +566,42 @@ class OrderingFilterTests(TestCase):
{'id': 1, 'title': 'zyx', 'text': 'abc'}, {'id': 1, 'title': 'zyx', 'text': 'abc'},
] ]
def test_ordering_without_ordering_fields(self):
class OrderingListView(generics.ListAPIView):
queryset = OrderingFilterModel.objects.all()
serializer_class = OrderingFilterSerializerWithModelProperty
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
view = OrderingListView.as_view()
# Model field ordering works fine.
request = factory.get('/', {'ordering': 'text'})
response = view(request)
assert response.data == [
{'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'},
]
# `incorrectfield` ordering works fine.
request = factory.get('/', {'ordering': 'foobar'})
response = view(request)
assert response.data == [
{'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'},
]
# `description` is a Model property, which should be ignored.
request = factory.get('/', {'ordering': 'description'})
response = view(request)
assert response.data == [
{'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'},
]
def test_default_ordering(self): def test_default_ordering(self):
class OrderingListView(generics.ListAPIView): class OrderingListView(generics.ListAPIView):
queryset = OrderingFilterModel.objects.all() queryset = OrderingFilterModel.objects.all()