Skip to content

Commit ce15683

Browse files
Ordering filter bug with model property serializer field (encode#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
1 parent b256c46 commit ce15683

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

rest_framework/filters.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,20 @@ def get_default_valid_fields(self, queryset, view, context={}):
226226
)
227227
raise ImproperlyConfigured(msg % self.__class__.__name__)
228228

229+
model_class = queryset.model
230+
model_property_names = [
231+
# 'pk' is a property added in Django's Model class, however it is valid for ordering.
232+
attr for attr in dir(model_class) if isinstance(getattr(model_class, attr), property) and attr != 'pk'
233+
]
234+
229235
return [
230236
(field.source.replace('.', '__') or field_name, field.label)
231237
for field_name, field in serializer_class(context=context).fields.items()
232-
if not getattr(field, 'write_only', False) and not field.source == '*'
238+
if (
239+
not getattr(field, 'write_only', False) and
240+
not field.source == '*' and
241+
field.source not in model_property_names
242+
)
233243
]
234244

235245
def get_valid_fields(self, queryset, view, context={}):

tests/test_filters.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,10 @@ class OrderingFilterModel(models.Model):
424424
title = models.CharField(max_length=20, verbose_name='verbose title')
425425
text = models.CharField(max_length=100)
426426

427+
@property
428+
def description(self):
429+
return self.title + ": " + self.text
430+
427431

428432
class OrderingFilterRelatedModel(models.Model):
429433
related_object = models.ForeignKey(OrderingFilterModel, related_name="relateds", on_delete=models.CASCADE)
@@ -436,6 +440,17 @@ class Meta:
436440
fields = '__all__'
437441

438442

443+
class OrderingFilterSerializerWithModelProperty(serializers.ModelSerializer):
444+
class Meta:
445+
model = OrderingFilterModel
446+
fields = (
447+
"id",
448+
"title",
449+
"text",
450+
"description"
451+
)
452+
453+
439454
class OrderingDottedRelatedSerializer(serializers.ModelSerializer):
440455
related_text = serializers.CharField(source='related_object.text')
441456
related_title = serializers.CharField(source='related_object.title')
@@ -551,6 +566,42 @@ class OrderingListView(generics.ListAPIView):
551566
{'id': 1, 'title': 'zyx', 'text': 'abc'},
552567
]
553568

569+
def test_ordering_without_ordering_fields(self):
570+
class OrderingListView(generics.ListAPIView):
571+
queryset = OrderingFilterModel.objects.all()
572+
serializer_class = OrderingFilterSerializerWithModelProperty
573+
filter_backends = (filters.OrderingFilter,)
574+
ordering = ('title',)
575+
576+
view = OrderingListView.as_view()
577+
578+
# Model field ordering works fine.
579+
request = factory.get('/', {'ordering': 'text'})
580+
response = view(request)
581+
assert response.data == [
582+
{'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'},
583+
{'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'},
584+
{'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'},
585+
]
586+
587+
# `incorrectfield` ordering works fine.
588+
request = factory.get('/', {'ordering': 'foobar'})
589+
response = view(request)
590+
assert response.data == [
591+
{'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'},
592+
{'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'},
593+
{'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'},
594+
]
595+
596+
# `description` is a Model property, which should be ignored.
597+
request = factory.get('/', {'ordering': 'description'})
598+
response = view(request)
599+
assert response.data == [
600+
{'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'},
601+
{'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'},
602+
{'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'},
603+
]
604+
554605
def test_default_ordering(self):
555606
class OrderingListView(generics.ListAPIView):
556607
queryset = OrderingFilterModel.objects.all()

0 commit comments

Comments
 (0)