-
Notifications
You must be signed in to change notification settings - Fork 298
Add filter feature per JSON API specs. #286
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 all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Added filter feature. This uses DjangoFilterBackend which either comes from `rest-framework-filter` or `django-filter`. The former is a package which adds more features to the latter. All that is done is reformatting the request query parameters to be compatible with DjangoFilterBackend. The spec recommend the pattern `filter[field]=values`. JsonApiFilterBackend takes that pattern and reformats it to become `field=value` which will then work as typical filtering in DRF. The specs only rule towards filtering is that the `filter` keyword should be reserved. It doesn't however say about the exact format. It only **recommends** `filter[field]=value` but this can also be `filter{field}=value` or `filter(field)=value`. Therefore, a new setting for DJA is introduced: JSON_API_FILTER_KEYWORD. This is a regex which controls the filter format. Its default value is: ``` JSON_API_FILTER_KEYWORD = 'filter\[(?P<field>\w+)\]' ``` It can be changed to anything as long as the `field` keyword is included in the regex. The docs have been updated. Unfortunately, the editor's beautifier was run and made lots of other changes to the code style. No harm was done though, that's why TDD exists :D
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,3 +37,8 @@ pip-delete-this-directory.txt | |
|
||
# VirtualEnv | ||
.venv/ | ||
|
||
#python3 pyvenv | ||
bin/ | ||
lib64 | ||
pyvenv.cfg |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import django_filters | ||
|
||
from example.models import Comment | ||
|
||
|
||
class CommentFilter(django_filters.FilterSet): | ||
|
||
|
||
class Meta: | ||
model = Comment | ||
fileds = {'body': ['exact', 'in', 'icontains', 'contains'], | ||
'author': ['exact', 'gte', 'lte'], | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
from django.core.urlresolvers import reverse | ||
|
||
from rest_framework.settings import api_settings | ||
|
||
import pytest | ||
|
||
from example.tests.utils import dump_json, redump_json | ||
|
||
pytestmark = pytest.mark.django_db | ||
|
||
|
||
class TestJsonApiFilter(object): | ||
|
||
def test_request_without_filter(self, client, comment_factory): | ||
comment = comment_factory() | ||
comment2 = comment_factory() | ||
|
||
expected = { | ||
"links": { | ||
"first": "http://testserver/comments?page=1", | ||
"last": "http://testserver/comments?page=2", | ||
"next": "http://testserver/comments?page=2", | ||
"prev": None | ||
}, | ||
"data": [ | ||
{ | ||
"type": "comments", | ||
"id": str(comment.pk), | ||
"attributes": { | ||
"body": comment.body | ||
}, | ||
"relationships": { | ||
"entry": { | ||
"data": { | ||
"type": "entries", | ||
"id": str(comment.entry.pk) | ||
} | ||
}, | ||
"author": { | ||
"data": { | ||
"type": "authors", | ||
"id": str(comment.author.pk) | ||
} | ||
}, | ||
} | ||
} | ||
], | ||
"meta": { | ||
"pagination": { | ||
"page": 1, | ||
"pages": 2, | ||
"count": 2 | ||
} | ||
} | ||
} | ||
|
||
response = client.get('/comments') | ||
# assert 0 | ||
|
||
assert response.status_code == 200 | ||
actual = redump_json(response.content) | ||
expected_json = dump_json(expected) | ||
assert actual == expected_json | ||
|
||
def test_request_with_filter(self, client, comment_factory): | ||
comment = comment_factory(body='Body for comment 1') | ||
comment2 = comment_factory() | ||
|
||
expected = { | ||
"links": { | ||
"first": "http://testserver/comments?filter%5Bbody%5D=Body+for+comment+1&page=1", | ||
"last": "http://testserver/comments?filter%5Bbody%5D=Body+for+comment+1&page=1", | ||
"next": None, | ||
"prev": None | ||
}, | ||
"data": [ | ||
{ | ||
"type": "comments", | ||
"id": str(comment.pk), | ||
"attributes": { | ||
"body": comment.body | ||
}, | ||
"relationships": { | ||
"entry": { | ||
"data": { | ||
"type": "entries", | ||
"id": str(comment.entry.pk) | ||
} | ||
}, | ||
"author": { | ||
"data": { | ||
"type": "authors", | ||
"id": str(comment.author.pk) | ||
} | ||
}, | ||
} | ||
} | ||
], | ||
"meta": { | ||
"pagination": { | ||
"page": 1, | ||
"pages": 1, | ||
"count": 1 | ||
} | ||
} | ||
} | ||
|
||
response = client.get('/comments?filter[body]=Body for comment 1') | ||
|
||
assert response.status_code == 200 | ||
actual = redump_json(response.content) | ||
expected_json = dump_json(expected) | ||
assert actual == expected_json | ||
|
||
def test_failed_request_with_filter(self, client, comment_factory): | ||
comment = comment_factory(body='Body for comment 1') | ||
comment2 = comment_factory() | ||
|
||
expected = { | ||
"links": { | ||
"first": "http://testserver/comments?filter%5Bbody%5D=random+comment&page=1", | ||
"last": "http://testserver/comments?filter%5Bbody%5D=random+comment&page=1", | ||
"next": None, | ||
"prev": None | ||
}, | ||
"data": [], | ||
"meta": { | ||
"pagination": { | ||
"page": 1, | ||
"pages": 1, | ||
"count": 0 | ||
} | ||
} | ||
} | ||
|
||
response = client.get('/comments?filter[body]=random comment') | ||
assert response.status_code == 200 | ||
actual = redump_json(response.content) | ||
expected_json = dump_json(expected) | ||
assert actual == expected_json |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,5 +3,6 @@ pytest>=2.9.0,<3.0 | |
pytest-django | ||
pytest-factoryboy | ||
fake-factory | ||
django-filter | ||
tox | ||
mock |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
try: | ||
import rest_framework_filters | ||
DjangoFilterBackend = rest_framework_filters.backends.DjangoFilterBackend | ||
except ImportError: | ||
from rest_framework import filters | ||
DjangoFilterBackend = filters.DjangoFilterBackend | ||
|
||
from rest_framework_json_api.utils import format_query_params | ||
|
||
class JsonApiFilterBackend(DjangoFilterBackend): | ||
|
||
def filter_queryset(self, request, queryset, view): | ||
|
||
filter_class = self.get_filter_class(view, queryset) | ||
new_query_params = format_query_params(request.query_params) | ||
if filter_class: | ||
return filter_class(new_query_params, queryset=queryset).qs | ||
|
||
return queryset |