-
Notifications
You must be signed in to change notification settings - Fork 301
JSONAPIOrderingFilter #459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
9fd9727
a38704d
8380dd2
2456cb8
c1c0c88
779aed9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
- model: example.blog | ||
pk: 1 | ||
fields: {name: ANTB, tagline: ANTHROPOLOGY (BARNARD), created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.blog | ||
pk: 2 | ||
fields: {name: CLSB, tagline: CLASSICS (BARNARD), created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.blog | ||
pk: 3 | ||
fields: {name: AMSB, tagline: AMERICAN STUDIES (BARNARD), created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.blog | ||
pk: 4 | ||
fields: {name: CHMB, tagline: CHEMISTRY (BARNARD), created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.blog | ||
pk: 5 | ||
fields: {name: ARHB, tagline: ART HISTORY (BARNARD), created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.blog | ||
pk: 6 | ||
fields: {name: ITLB, tagline: ITALIAN (BARNARD), created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.blog | ||
pk: 7 | ||
fields: {name: BIOB, tagline: BIOLOGICAL SCIENCES (BARNARD), created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 1 | ||
fields: {blog: 1, headline: ANTH1009V, body_text: INTRO TO LANGUAGE & CULTURE, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 2 | ||
fields: {blog: 2, headline: CLCV2442V, body_text: EGYPT IN CLASSICAL WORLD-DISC, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 3 | ||
fields: {blog: 3, headline: AMST3704X, body_text: SENIOR RESEARCH ESSAY SEMINAR, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 4 | ||
fields: {blog: 1, headline: ANTH3976V, body_text: ANTHROPOLOGY OF SCIENCE, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 5 | ||
fields: {blog: 4, headline: CHEM3271X, body_text: INORGANIC CHEMISTRY, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 6 | ||
fields: {blog: 5, headline: AHIS3915X, body_text: ISLAM AND MEDIEVAL WEST, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 7 | ||
fields: {blog: 1, headline: ANTH3868X, body_text: ETHNOGRAPHIC FIELD RESEARCH IN | ||
NYC, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 8 | ||
fields: {blog: 6, headline: CLIA3660V, body_text: MAFIA MOVIES, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 9 | ||
fields: {blog: 5, headline: AHIS3999X, body_text: INDEPENDENT RESEARCH, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
- model: example.entry | ||
pk: 10 | ||
fields: {blog: 7, headline: BIOL3594X, body_text: SENIOR THESIS SEMINAR, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
# a null body_text: | ||
- model: example.entry | ||
pk: 11 | ||
fields: {blog: 7, headline: BIOL9999X, body_text: null, created_at: '2018-08-20', modified_at: '2018-08-20'} | ||
# an empty body_text: | ||
- model: example.entry | ||
pk: 12 | ||
fields: {blog: 7, headline: BIOL0000X, body_text: "", created_at: '2018-08-20', modified_at: '2018-08-20'} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import json | ||
|
||
from rest_framework.test import APITestCase | ||
|
||
from ..models import Blog, Entry | ||
|
||
ENTRIES = "/nopage-entries" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we shouldn't build routes manually but instead use django reverse resolver In this case this would be This needs to be done in setUp or what I usually do in each single test as it is more implicit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe |
||
|
||
|
||
class DJATestParameters(APITestCase): | ||
""" | ||
tests of JSON:API backends | ||
""" | ||
fixtures = ('blogentry',) | ||
|
||
def setUp(self): | ||
self.entries = Entry.objects.all() | ||
self.blogs = Blog.objects.all() | ||
|
||
def test_sort(self): | ||
""" | ||
test sort | ||
""" | ||
response = self.client.get(ENTRIES + '?sort=headline') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parameters can be passed on as data e.g. in this case data={'sort': 'headline'}. This way there is no need for string concats |
||
self.assertEqual(response.status_code, 200, | ||
msg=response.content.decode("utf-8")) | ||
j = json.loads(response.content.decode("utf-8")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to get json result from response you should be able to simply do The same counts to the tests below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've seen cases where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but this is not one of them. |
||
headlines = [c['attributes']['headline'] for c in j['data']] | ||
sorted_headlines = [c['attributes']['headline'] for c in j['data']] | ||
sorted_headlines.sort() | ||
self.assertEqual(headlines, sorted_headlines) | ||
|
||
def test_sort_reverse(self): | ||
""" | ||
confirm switching the sort order actually works | ||
""" | ||
response = self.client.get(ENTRIES + '?sort=-headline') | ||
self.assertEqual(response.status_code, 200, | ||
msg=response.content.decode("utf-8")) | ||
j = json.loads(response.content.decode("utf-8")) | ||
headlines = [c['attributes']['headline'] for c in j['data']] | ||
sorted_headlines = [c['attributes']['headline'] for c in j['data']] | ||
sorted_headlines.sort() | ||
self.assertNotEqual(headlines, sorted_headlines) | ||
|
||
def test_sort_invalid(self): | ||
""" | ||
test sort of invalid field | ||
""" | ||
response = self.client.get( | ||
ENTRIES + '?sort=nonesuch,headline,-not_a_field') | ||
self.assertEqual(response.status_code, 400, | ||
msg=response.content.decode("utf-8")) | ||
j = json.loads(response.content.decode("utf-8")) | ||
self.assertEqual(j['errors'][0]['detail'], | ||
"invalid sort parameters: nonesuch,-not_a_field") |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,3 +14,4 @@ Sphinx | |
sphinx_rtd_theme | ||
tox | ||
twine | ||
PyYAML |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from rest_framework.exceptions import ValidationError | ||
from rest_framework.filters import OrderingFilter | ||
|
||
from rest_framework_json_api.utils import format_value | ||
|
||
|
||
class JSONAPIOrderingFilter(OrderingFilter): | ||
""" | ||
This implements http://jsonapi.org/format/#fetching-sorting and raises 400 | ||
if any sort field is invalid. If you prefer *not* to report 400 errors for | ||
invalid sort fields, just use OrderingFilter with `ordering_param='sort'` | ||
|
||
TODO: Add sorting based upon relationships (sort=relname.fieldname) | ||
""" | ||
ordering_param = 'sort' | ||
|
||
def remove_invalid_fields(self, queryset, fields, view, request): | ||
""" | ||
overrides remove_invalid_fields to raise a 400 exception instead of | ||
silently removing them. set `ignore_bad_sort_fields = True` to not | ||
do this validation. | ||
""" | ||
valid_fields = [ | ||
item[0] for item in self.get_valid_fields(queryset, view, | ||
{'request': request}) | ||
] | ||
bad_terms = [ | ||
term for term in fields | ||
if format_value(term.lstrip('-'), "underscore") not in valid_fields | ||
] | ||
if bad_terms: | ||
raise ValidationError('invalid sort parameter{}: {}'.format( | ||
('s' if len(bad_terms) > 1 else ''), ','.join(bad_terms))) | ||
|
||
return super(JSONAPIOrderingFilter, self).remove_invalid_fields( | ||
queryset, fields, view, request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's also do this in JSON as in drf_example