@@ -17,6 +17,7 @@
# along with Patchwork; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import datetime
import unittest
from django.conf import settings
@@ -161,7 +162,7 @@ class TestPersonAPI(APITestCase):
class TestPatchAPI(APITestCase):
fixtures = ['default_states']
- def api_url(self, item=None):
+ def api_url(self, item=None, query=None):
if item is None:
return reverse('api_1.0:patch-list')
return reverse('api_1.0:patch-detail', args=[item])
@@ -184,6 +185,29 @@ class TestPatchAPI(APITestCase):
patch = resp.data['results'][0]
self.assertEqual(patches[0].name, patch['name'])
+ def test_query_filters(self):
+ """Test out our filtering support."""
+ patches = create_patches(4)
+ project = Project.objects.create(
+ linkname='foo', name='Foo', listid='foo.example.com')
+ patches[0].project = project
+ patches[0].save()
+ resp = self.client.get(self.api_url() + '?project=Foo')
+ self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ self.assertEqual(1, resp.data['count'])
+ self.assertEqual(patches[0].id, resp.data['results'][0]['id'])
+
+ ts = datetime.datetime.now().isoformat()
+ new_patches = create_patches(2)
+
+ resp = self.client.get(self.api_url() + '?until=%s' % ts)
+ self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ self.assertEqual(len(patches), resp.data['count'])
+
+ resp = self.client.get(self.api_url() + '?since=%s' % ts)
+ self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ self.assertEqual(len(new_patches), resp.data['count'])
+
def test_get(self):
"""Validate we can get a specific project."""
patches = create_patches()
@@ -59,14 +59,50 @@ class AuthenticatedReadOnly(permissions.BasePermission):
class PatchworkViewSet(ModelViewSet):
pagination_class = PageSizePagination
+ # a dict of the request-query-param -> django ORM query parameter
+ query_filters = {}
+
def get_queryset(self):
- return self.serializer_class.Meta.model.objects.all()
+ qs = self.serializer_class.Meta.model.objects.all()
+ filters = {}
+ for param, val in self.request.query_params.items():
+ name = self.query_filters.get(param)
+ if name:
+ filters[name] = val
+ return qs.filter(**filters)
+
+ def options(self, request, *args, **kwargs):
+ # add our query filters to make the "options" command a little more
+ # helpful.
+ resp = super(PatchworkViewSet, self).options(request, *args, **kwargs)
+ if self.query_filters:
+ resp.data['query_filters'] = self.query_filters.keys()
+ return resp
class PatchViewSet(PatchworkViewSet):
+ """Listings support the following query filters:
+ * project=<project-name>
+ * since=<YYYY-MM-DDTHH:MM:SS.mm>
+ * until=<YYYY-MM-DDTHH:MM:SS.mm>
+ * state=<state-name>
+ * submitter=<name>
+ * delegate=<name>
+
+ eg: GET /api/1.0/patches/?project=p&since=2016-01-01&submitter=User+Name
+ """
permission_classes = (PatchworkPermission,)
serializer_class = PatchSerializer
+ query_filters = {
+ 'project': 'project__name',
+ 'submitter': 'submitter__name',
+ 'delegate': 'delegate__name',
+ 'state': 'state__name',
+ 'since': 'date__gt',
+ 'until': 'date__lt',
+ }
+
class PeopleViewSet(PatchworkViewSet):
permission_classes = (AuthenticatedReadOnly,)
This adds generic filtering support to the PatchworkViewSet as well as useful filtering options for listing patches. DRF has some filter capabilities, but they can really slow down the web interface for large data sets and are a little complex. This approach is pretty simple and works efficiently. The "options" method is now overridden to show the user available filters when supported. Signed-off-by: Andy Doan <andy.doan@linaro.org> --- patchwork/tests/test_rest_api.py | 26 +++++++++++++++++++++++++- patchwork/views/rest_api.py | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-)