This means we don't need to make a request to '/people' to filter things like patches or series.
Signed-off-by: Stephen Finucane <step...@that.guru> --- patchwork/api/filters.py | 39 ++++++++++++++++++--- patchwork/tests/test_rest_api.py | 40 ++++++++++++++++++++-- .../improved-rest-filtering-bf68399270a9b245.yaml | 9 +++++ 3 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/improved-rest-filtering-bf68399270a9b245.yaml diff --git a/patchwork/api/filters.py b/patchwork/api/filters.py index 198d64f4..c34f5496 100644 --- a/patchwork/api/filters.py +++ b/patchwork/api/filters.py @@ -29,6 +29,7 @@ from patchwork.models import Check from patchwork.models import CoverLetter from patchwork.models import Event from patchwork.models import Patch +from patchwork.models import Person from patchwork.models import Project from patchwork.models import Series from patchwork.models import State @@ -41,16 +42,16 @@ class TimestampMixin(FilterSet): since = IsoDateTimeFilter(name='date', **{LOOKUP_FIELD: 'gte'}) -class ProjectChoiceField(ModelChoiceField): +class ModelMultiChoiceField(ModelChoiceField): + + def _get_filters(self, value): + raise NotImplementedError def to_python(self, value): if value in self.empty_values: return None - try: - filters = {'pk': int(value)} - except ValueError: - filters = {'linkname__iexact': value} + filters = self._get_filters(value) try: value = self.queryset.get(**filters) @@ -60,6 +61,15 @@ class ProjectChoiceField(ModelChoiceField): return value +class ProjectChoiceField(ModelMultiChoiceField): + + def _get_filters(self, value): + try: + return {'pk': int(value)} + except ValueError: + return {'linkname__iexact': value} + + class ProjectFilter(ModelChoiceFilter): field_class = ProjectChoiceField @@ -71,8 +81,24 @@ class ProjectMixin(FilterSet): queryset=Project.objects.all()) +class PersonChoiceField(ModelMultiChoiceField): + + def _get_filters(self, value): + try: + return {'pk': int(value)} + except ValueError: + return {'email__iexact': value} + + +class PersonFilter(ModelChoiceFilter): + + field_class = PersonChoiceField + + class SeriesFilter(ProjectMixin, TimestampMixin, FilterSet): + submitter = PersonFilter(queryset=Person.objects.all()) + class Meta: model = Series fields = ('submitter', 'project') @@ -80,6 +106,8 @@ class SeriesFilter(ProjectMixin, TimestampMixin, FilterSet): class CoverLetterFilter(ProjectMixin, TimestampMixin, FilterSet): + submitter = PersonFilter(queryset=Person.objects.all()) + class Meta: model = CoverLetter fields = ('project', 'series', 'submitter') @@ -113,6 +141,7 @@ class StateFilter(ModelChoiceFilter): class PatchFilter(ProjectMixin, TimestampMixin, FilterSet): state = StateFilter(queryset=State.objects.all()) + submitter = PersonFilter(queryset=Person.objects.all()) class Meta: model = Patch diff --git a/patchwork/tests/test_rest_api.py b/patchwork/tests/test_rest_api.py index 87112d9f..7d10f909 100644 --- a/patchwork/tests/test_rest_api.py +++ b/patchwork/tests/test_rest_api.py @@ -341,9 +341,11 @@ class TestPatchAPI(APITestCase): self.assertEqual(status.HTTP_200_OK, resp.status_code) self.assertEqual(0, len(resp.data)) + person_obj = create_person(email='t...@example.com') state_obj = create_state(name='Under Review') project_obj = create_project(linkname='myproject') - patch_obj = create_patch(state=state_obj, project=project_obj) + patch_obj = create_patch(state=state_obj, project=project_obj, + submitter=person_obj) # anonymous user resp = self.client.get(self.api_url()) @@ -376,6 +378,16 @@ class TestPatchAPI(APITestCase): resp = self.client.get(self.api_url(), {'project': 'invalidproject'}) self.assertEqual(0, len(resp.data)) + # test filtering by submitter, both ID and email + resp = self.client.get(self.api_url(), {'submitter': person_obj.id}) + self.assertEqual([patch_obj.id], [x['id'] for x in resp.data]) + resp = self.client.get(self.api_url(), { + 'submitter': 't...@example.com'}) + self.assertEqual([patch_obj.id], [x['id'] for x in resp.data]) + resp = self.client.get(self.api_url(), { + 'submitter': 't...@example.org'}) + self.assertEqual(0, len(resp.data)) + def test_detail(self): """Validate we can get a specific patch.""" patch = create_patch( @@ -493,8 +505,9 @@ class TestCoverLetterAPI(APITestCase): self.assertEqual(status.HTTP_200_OK, resp.status_code) self.assertEqual(0, len(resp.data)) + person_obj = create_person(email='t...@example.com') project_obj = create_project(linkname='myproject') - cover_obj = create_cover(project=project_obj) + cover_obj = create_cover(project=project_obj, submitter=person_obj) # anonymous user resp = self.client.get(self.api_url()) @@ -516,6 +529,16 @@ class TestCoverLetterAPI(APITestCase): resp = self.client.get(self.api_url(), {'project': 'invalidproject'}) self.assertEqual(0, len(resp.data)) + # test filtering by submitter, both ID and email + resp = self.client.get(self.api_url(), {'submitter': person_obj.id}) + self.assertEqual([cover_obj.id], [x['id'] for x in resp.data]) + resp = self.client.get(self.api_url(), { + 'submitter': 't...@example.com'}) + self.assertEqual([cover_obj.id], [x['id'] for x in resp.data]) + resp = self.client.get(self.api_url(), { + 'submitter': 't...@example.org'}) + self.assertEqual(0, len(resp.data)) + def test_detail(self): """Validate we can get a specific cover letter.""" cover_obj = create_cover() @@ -574,8 +597,9 @@ class TestSeriesAPI(APITestCase): self.assertEqual(status.HTTP_200_OK, resp.status_code) self.assertEqual(0, len(resp.data)) + person_obj = create_person(email='t...@example.com') project_obj = create_project(linkname='myproject') - series_obj = create_series(project=project_obj) + series_obj = create_series(project=project_obj, submitter=person_obj) # anonymous user resp = self.client.get(self.api_url()) @@ -597,6 +621,16 @@ class TestSeriesAPI(APITestCase): resp = self.client.get(self.api_url(), {'project': 'invalidproject'}) self.assertEqual(0, len(resp.data)) + # test filtering by submitter, both ID and email + resp = self.client.get(self.api_url(), {'submitter': person_obj.id}) + self.assertEqual([series_obj.id], [x['id'] for x in resp.data]) + resp = self.client.get(self.api_url(), { + 'submitter': 't...@example.com'}) + self.assertEqual([series_obj.id], [x['id'] for x in resp.data]) + resp = self.client.get(self.api_url(), { + 'submitter': 't...@example.org'}) + self.assertEqual(0, len(resp.data)) + def test_detail(self): """Validate we can get a specific series.""" series = create_series() diff --git a/releasenotes/notes/improved-rest-filtering-bf68399270a9b245.yaml b/releasenotes/notes/improved-rest-filtering-bf68399270a9b245.yaml new file mode 100644 index 00000000..fda68790 --- /dev/null +++ b/releasenotes/notes/improved-rest-filtering-bf68399270a9b245.yaml @@ -0,0 +1,9 @@ +--- +api: + - | + Series, patches and cover letters can be filtered by submitter using email + addresses. For example: + + .. code-block:: shell + + $ curl /covers/?submitter=step...@that.guru -- 2.14.3 _______________________________________________ Patchwork mailing list Patchwork@lists.ozlabs.org https://lists.ozlabs.org/listinfo/patchwork