Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 17d65b8

Browse files
authored
Merge pull request django-json-api#464 from n2ygk/refactor_backends_filters
Refactor backends to filters + bugfix
2 parents e6290af + d77b17a commit 17d65b8

File tree

6 files changed

+139
-66
lines changed

6 files changed

+139
-66
lines changed

‎docs/usage.md‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ REST_FRAMEWORK = {
3333
),
3434
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
3535
'DEFAULT_FILTER_BACKENDS': (
36-
'rest_framework_json_api.backends.JSONAPIOrderingFilter',
36+
'rest_framework_json_api.filters.JSONAPIOrderingFilter',
3737
),
3838
'TEST_REQUEST_RENDERER_CLASSES': (
3939
'rest_framework_json_api.renderers.JSONRenderer',
@@ -98,7 +98,7 @@ _This is the first of several anticipated JSON:API-specific filter backends._
9898
`JSONAPIOrderingFilter` implements the [JSON:API `sort`](http://jsonapi.org/format/#fetching-sorting) and uses
9999
DRF's [ordering filter](http://django-rest-framework.readthedocs.io/en/latest/api-guide/filtering/#orderingfilter).
100100

101-
Per the JSON:API, "If the server does not support sorting as specified in the query parameter `sort`,
101+
Per the JSON:API specification, "If the server does not support sorting as specified in the query parameter `sort`,
102102
it **MUST** return `400 Bad Request`." For example, for `?sort=`abc,foo,def` where `foo` is a valid
103103
field name and the other two are not valid:
104104
```json
@@ -118,6 +118,20 @@ field name and the other two are not valid:
118118
If you want to silently ignore bad sort fields, just use `rest_framework.filters.OrderingFilter` and set
119119
`ordering_param` to `sort`.
120120

121+
#### Configuring Filter Backends
122+
123+
You can configure the filter backends either by setting the `REST_FRAMEWORK['DEFAULT_FILTER_BACKENDS']` as shown
124+
in the [preceding](#configuration) example or individually add them as `.filter_backends` View attributes:
125+
126+
```python
127+
from rest_framework_json_api import filters
128+
129+
class MyViewset(ModelViewSet):
130+
queryset = MyModel.objects.all()
131+
serializer_class = MyModelSerializer
132+
filter_backends = (filters.JSONAPIOrderingFilter,)
133+
```
134+
121135

122136
### Performance Testing
123137

‎example/settings/dev.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,8 @@
8989
),
9090
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
9191
'DEFAULT_FILTER_BACKENDS': (
92-
'rest_framework_json_api.backends.JSONAPIOrderingFilter',
92+
'rest_framework_json_api.filters.JSONAPIOrderingFilter',
9393
),
94-
'ORDERING_PARAM': 'sort',
9594
'TEST_REQUEST_RENDERER_CLASSES': (
9695
'rest_framework_json_api.renderers.JSONRenderer',
9796
),

‎example/tests/test_backends.py‎

Lines changed: 0 additions & 54 deletions
This file was deleted.

‎example/tests/test_filters.py‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from rest_framework.reverse import reverse
2+
from rest_framework.test import APITestCase
3+
4+
from ..models import Blog, Entry
5+
6+
7+
class DJATestParameters(APITestCase):
8+
"""
9+
tests of JSON:API backends
10+
"""
11+
fixtures = ('blogentry',)
12+
13+
def setUp(self):
14+
self.entries = Entry.objects.all()
15+
self.blogs = Blog.objects.all()
16+
self.url = reverse('nopage-entry-list')
17+
18+
def test_sort(self):
19+
"""
20+
test sort
21+
"""
22+
response = self.client.get(self.url, data={'sort': 'headline'})
23+
self.assertEqual(response.status_code, 200,
24+
msg=response.content.decode("utf-8"))
25+
dja_response = response.json()
26+
headlines = [c['attributes']['headline'] for c in dja_response['data']]
27+
sorted_headlines = sorted(headlines)
28+
self.assertEqual(headlines, sorted_headlines)
29+
30+
def test_sort_reverse(self):
31+
"""
32+
confirm switching the sort order actually works
33+
"""
34+
response = self.client.get(self.url, data={'sort': '-headline'})
35+
self.assertEqual(response.status_code, 200,
36+
msg=response.content.decode("utf-8"))
37+
dja_response = response.json()
38+
headlines = [c['attributes']['headline'] for c in dja_response['data']]
39+
sorted_headlines = sorted(headlines)
40+
self.assertNotEqual(headlines, sorted_headlines)
41+
42+
def test_sort_double_negative(self):
43+
"""
44+
what if they provide multiple `-`'s? It's OK.
45+
"""
46+
response = self.client.get(self.url, data={'sort': '--headline'})
47+
self.assertEqual(response.status_code, 200,
48+
msg=response.content.decode("utf-8"))
49+
dja_response = response.json()
50+
headlines = [c['attributes']['headline'] for c in dja_response['data']]
51+
sorted_headlines = sorted(headlines)
52+
self.assertNotEqual(headlines, sorted_headlines)
53+
54+
def test_sort_invalid(self):
55+
"""
56+
test sort of invalid field
57+
"""
58+
response = self.client.get(self.url,
59+
data={'sort': 'nonesuch,headline,-not_a_field'})
60+
self.assertEqual(response.status_code, 400,
61+
msg=response.content.decode("utf-8"))
62+
dja_response = response.json()
63+
self.assertEqual(dja_response['errors'][0]['detail'],
64+
"invalid sort parameters: nonesuch,-not_a_field")
65+
66+
def test_sort_camelcase(self):
67+
"""
68+
test sort of camelcase field name
69+
"""
70+
response = self.client.get(self.url, data={'sort': 'bodyText'})
71+
self.assertEqual(response.status_code, 200,
72+
msg=response.content.decode("utf-8"))
73+
dja_response = response.json()
74+
blog_ids = [(c['attributes']['bodyText'] or '') for c in dja_response['data']]
75+
sorted_blog_ids = sorted(blog_ids)
76+
self.assertEqual(blog_ids, sorted_blog_ids)
77+
78+
def test_sort_underscore(self):
79+
"""
80+
test sort of underscore field name
81+
Do we allow this notation in a search even if camelcase is in effect?
82+
"Be conservative in what you send, be liberal in what you accept"
83+
-- https://en.wikipedia.org/wiki/Robustness_principle
84+
"""
85+
response = self.client.get(self.url, data={'sort': 'body_text'})
86+
self.assertEqual(response.status_code, 200,
87+
msg=response.content.decode("utf-8"))
88+
dja_response = response.json()
89+
blog_ids = [(c['attributes']['bodyText'] or '') for c in dja_response['data']]
90+
sorted_blog_ids = sorted(blog_ids)
91+
self.assertEqual(blog_ids, sorted_blog_ids)
92+
93+
def test_sort_related(self):
94+
"""
95+
test sort via related field using jsonapi path `.` and django orm `__` notation.
96+
ORM relations must be predefined in the View's .ordering_fields attr
97+
"""
98+
for datum in ('blog__id', 'blog.id'):
99+
response = self.client.get(self.url, data={'sort': datum})
100+
self.assertEqual(response.status_code, 200,
101+
msg=response.content.decode("utf-8"))
102+
dja_response = response.json()
103+
blog_ids = [c['relationships']['blog']['data']['id'] for c in dja_response['data']]
104+
sorted_blog_ids = sorted(blog_ids)
105+
self.assertEqual(blog_ids, sorted_blog_ids)

‎example/views.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class NoPagination(PageNumberPagination):
9090

9191
class NonPaginatedEntryViewSet(EntryViewSet):
9292
pagination_class = NoPagination
93+
ordering_fields = ('headline', 'body_text', 'blog__name', 'blog__id')
9394

9495

9596
class AuthorViewSet(ModelViewSet):

‎rest_framework_json_api/backends.py‎ renamed to ‎rest_framework_json_api/filters.py‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,35 @@ class JSONAPIOrderingFilter(OrderingFilter):
1010
if any sort field is invalid. If you prefer *not* to report 400 errors for
1111
invalid sort fields, just use OrderingFilter with `ordering_param='sort'`
1212
13-
TODO: Add sorting based upon relationships (sort=relname.fieldname)
13+
Also applies DJA format_value() to convert (e.g. camelcase) to underscore.
14+
(See JSON_API_FORMAT_FIELD_NAMES in docs/usage.md)
1415
"""
1516
ordering_param = 'sort'
1617

1718
def remove_invalid_fields(self, queryset, fields, view, request):
18-
"""
19-
overrides remove_invalid_fields to raise a 400 exception instead of
20-
silently removing them. set `ignore_bad_sort_fields = True` to not
21-
do this validation.
22-
"""
2319
valid_fields = [
2420
item[0] for item in self.get_valid_fields(queryset, view,
2521
{'request': request})
2622
]
2723
bad_terms = [
2824
term for term in fields
29-
if format_value(term.lstrip('-'), "underscore") not in valid_fields
25+
if format_value(term.replace(".", "__").lstrip('-'), "underscore") not in valid_fields
3026
]
3127
if bad_terms:
3228
raise ValidationError('invalid sort parameter{}: {}'.format(
3329
('s' if len(bad_terms) > 1 else ''), ','.join(bad_terms)))
30+
# this looks like it duplicates code above, but we want the ValidationError to report
31+
# the actual parameter supplied while we want the fields passed to the super() to
32+
# be correctly rewritten.
33+
# The leading `-` has to be stripped to prevent format_value from turning it into `_`.
34+
underscore_fields = []
35+
for item in fields:
36+
item_rewritten = item.replace(".", "__")
37+
if item_rewritten.startswith('-'):
38+
underscore_fields.append(
39+
'-' + format_value(item_rewritten.lstrip('-'), "underscore"))
40+
else:
41+
underscore_fields.append(format_value(item_rewritten, "underscore"))
3442

3543
return super(JSONAPIOrderingFilter, self).remove_invalid_fields(
36-
queryset, fields, view, request)
44+
queryset, underscore_fields, view, request)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /