This closes out the labels feature. Signed-off-by: Stephen Finucane <step...@that.guru> Closes: #22 --- v2: - Expose for cover letters as well as patches - Add unit tests - Add release note - Use 'StringRelatedField' to avoid need for a labels endpoint --- patchwork/api/cover.py | 5 +++- patchwork/api/patch.py | 11 ++++--- patchwork/tests/api/test_cover.py | 29 +++++++++++++++++++ patchwork/tests/api/test_patch.py | 28 ++++++++++++++++++ .../notes/labels-6d0096c7d8505627.yaml | 7 +++++ 5 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/patchwork/api/cover.py b/patchwork/api/cover.py index 40f8c351..cdf3d6b7 100644 --- a/patchwork/api/cover.py +++ b/patchwork/api/cover.py @@ -9,6 +9,7 @@ from rest_framework.generics import ListAPIView from rest_framework.generics import RetrieveAPIView from rest_framework.reverse import reverse from rest_framework.serializers import SerializerMethodField +from rest_framework.serializers import StringRelatedField from patchwork.api.base import BaseHyperlinkedModelSerializer from patchwork.api.filters import CoverLetterFilterSet @@ -26,6 +27,7 @@ class CoverLetterListSerializer(BaseHyperlinkedModelSerializer): mbox = SerializerMethodField() series = SeriesSerializer(many=True, read_only=True) comments = SerializerMethodField() + labels = StringRelatedField(many=True) def get_web_url(self, instance): request = self.context.get('request') @@ -42,10 +44,11 @@ class CoverLetterListSerializer(BaseHyperlinkedModelSerializer): class Meta: model = CoverLetter fields = ('id', 'url', 'web_url', 'project', 'msgid', 'date', 'name', - 'submitter', 'mbox', 'series', 'comments') + 'submitter', 'mbox', 'series', 'comments', 'labels') read_only_fields = fields versioned_fields = { '1.1': ('web_url', 'mbox', 'comments'), + '1.2': ('labels',) } extra_kwargs = { 'url': {'view_name': 'api-cover-detail'}, diff --git a/patchwork/api/patch.py b/patchwork/api/patch.py index 1e647283..9663dcd2 100644 --- a/patchwork/api/patch.py +++ b/patchwork/api/patch.py @@ -11,6 +11,7 @@ from rest_framework.generics import RetrieveUpdateAPIView from rest_framework.relations import RelatedField from rest_framework.reverse import reverse from rest_framework.serializers import SerializerMethodField +from rest_framework.serializers import StringRelatedField from patchwork.api.base import BaseHyperlinkedModelSerializer from patchwork.api.base import PatchworkPermission @@ -74,6 +75,7 @@ class PatchListSerializer(BaseHyperlinkedModelSerializer): check = SerializerMethodField() checks = SerializerMethodField() tags = SerializerMethodField() + labels = StringRelatedField(many=True) def get_web_url(self, instance): request = self.context.get('request') @@ -104,12 +106,13 @@ class PatchListSerializer(BaseHyperlinkedModelSerializer): fields = ('id', 'url', 'web_url', 'project', 'msgid', 'date', 'name', 'commit_ref', 'pull_url', 'state', 'archived', 'hash', 'submitter', 'delegate', 'mbox', 'series', 'comments', - 'check', 'checks', 'tags') + 'check', 'checks', 'tags', 'labels') read_only_fields = ('web_url', 'project', 'msgid', 'date', 'name', 'hash', 'submitter', 'mbox', 'series', 'comments', - 'check', 'checks', 'tags') + 'check', 'checks', 'tags', 'labels') versioned_fields = { '1.1': ('comments', 'web_url'), + '1.2': ('labels', ), } extra_kwargs = { 'url': {'view_name': 'api-patch-detail'}, @@ -161,7 +164,7 @@ class PatchList(ListAPIView): def get_queryset(self): return Patch.objects.all()\ - .prefetch_related('series', 'check_set')\ + .prefetch_related('series', 'labels', 'check_set')\ .select_related('project', 'state', 'submitter', 'delegate')\ .defer('content', 'diff', 'headers') @@ -174,5 +177,5 @@ class PatchDetail(RetrieveUpdateAPIView): def get_queryset(self): return Patch.objects.all()\ - .prefetch_related('series', 'check_set')\ + .prefetch_related('series', 'labels', 'check_set')\ .select_related('project', 'state', 'submitter', 'delegate') diff --git a/patchwork/tests/api/test_cover.py b/patchwork/tests/api/test_cover.py index a5686292..31245649 100644 --- a/patchwork/tests/api/test_cover.py +++ b/patchwork/tests/api/test_cover.py @@ -10,6 +10,7 @@ from django.conf import settings from django.urls import reverse from patchwork.tests.utils import create_cover +from patchwork.tests.utils import create_label from patchwork.tests.utils import create_maintainer from patchwork.tests.utils import create_person from patchwork.tests.utils import create_project @@ -46,6 +47,11 @@ class TestCoverLetterAPI(APITestCase): self.assertIn(cover_obj.get_absolute_url(), cover_json['web_url']) self.assertIn('comments', cover_json) + # list fields + + for label in cover_obj.labels.all(): + self.assertIn(label.name, cover_json['labels']) + # nested fields self.assertEqual(cover_obj.submitter.id, @@ -57,9 +63,11 @@ class TestCoverLetterAPI(APITestCase): self.assertEqual(status.HTTP_200_OK, resp.status_code) self.assertEqual(0, len(resp.data)) + label_obj = create_label() person_obj = create_person(email='t...@example.com') project_obj = create_project(linkname='myproject') cover_obj = create_cover(project=project_obj, submitter=person_obj) + cover_obj.labels.add(label_obj) # anonymous user resp = self.client.get(self.api_url()) @@ -100,6 +108,18 @@ class TestCoverLetterAPI(APITestCase): self.assertIn('url', resp.data[0]) self.assertNotIn('mbox', resp.data[0]) self.assertNotIn('web_url', resp.data[0]) + self.assertNotIn('labels', resp.data[0]) + + def test_list_version_1_1(self): + create_cover() + + resp = self.client.get(self.api_url(version='1.1')) + self.assertEqual(status.HTTP_200_OK, resp.status_code) + self.assertEqual(1, len(resp.data)) + self.assertIn('url', resp.data[0]) + self.assertIn('mbox', resp.data[0]) + self.assertIn('web_url', resp.data[0]) + self.assertNotIn('labels', resp.data[0]) def test_detail(self): """Validate we can get a specific cover letter.""" @@ -126,6 +146,15 @@ class TestCoverLetterAPI(APITestCase): self.assertNotIn('web_url', resp.data) self.assertNotIn('comments', resp.data) + def test_detail_version_1_1(self): + cover = create_cover() + + resp = self.client.get(self.api_url(cover.id, version='1.1')) + self.assertIn('url', resp.data) + self.assertIn('web_url', resp.data) + self.assertIn('comments', resp.data) + self.assertNotIn('labels', resp.data) + def test_create_update_delete(self): user = create_maintainer() user.is_superuser = True diff --git a/patchwork/tests/api/test_patch.py b/patchwork/tests/api/test_patch.py index 3d6dad9c..00780c06 100644 --- a/patchwork/tests/api/test_patch.py +++ b/patchwork/tests/api/test_patch.py @@ -11,6 +11,7 @@ from django.conf import settings from django.urls import reverse from patchwork.models import Patch +from patchwork.tests.utils import create_label from patchwork.tests.utils import create_maintainer from patchwork.tests.utils import create_patch from patchwork.tests.utils import create_person @@ -51,6 +52,11 @@ class TestPatchAPI(APITestCase): self.assertIn(patch_obj.get_absolute_url(), patch_json['web_url']) self.assertIn('comments', patch_json) + # list fields + + for label in patch_obj.labels.all(): + self.assertIn(label.name, patch_json['labels']) + # nested fields self.assertEqual(patch_obj.submitter.id, @@ -64,11 +70,13 @@ class TestPatchAPI(APITestCase): self.assertEqual(status.HTTP_200_OK, resp.status_code) self.assertEqual(0, len(resp.data)) + label_obj = create_label() person_obj = create_person(email='t...@example.com') project_obj = create_project(linkname='myproject') state_obj = create_state(name='Under Review') patch_obj = create_patch(state=state_obj, project=project_obj, submitter=person_obj) + patch_obj.labels.add(label_obj) # anonymous user resp = self.client.get(self.api_url()) @@ -132,6 +140,16 @@ class TestPatchAPI(APITestCase): self.assertEqual(1, len(resp.data)) self.assertIn('url', resp.data[0]) self.assertNotIn('web_url', resp.data[0]) + self.assertNotIn('labels', resp.data[0]) + + def test_list_version_1_1(self): + create_patch() + + resp = self.client.get(self.api_url(version='1.1')) + self.assertEqual(status.HTTP_200_OK, resp.status_code) + self.assertEqual(1, len(resp.data)) + self.assertIn('web_url', resp.data[0]) + self.assertNotIn('labels', resp.data[0]) def test_detail(self): """Validate we can get a specific patch.""" @@ -161,6 +179,16 @@ class TestPatchAPI(APITestCase): self.assertIn('url', resp.data) self.assertNotIn('web_url', resp.data) self.assertNotIn('comments', resp.data) + self.assertNotIn('labels', resp.data) + + def test_detail_version_1_1(self): + patch = create_patch() + + resp = self.client.get(self.api_url(item=patch.id, version='1.1')) + self.assertIn('url', resp.data) + self.assertIn('web_url', resp.data) + self.assertIn('comments', resp.data) + self.assertNotIn('labels', resp.data) def test_create(self): """Ensure creations are rejected.""" diff --git a/releasenotes/notes/labels-6d0096c7d8505627.yaml b/releasenotes/notes/labels-6d0096c7d8505627.yaml index fdebd6b7..cb8a9213 100644 --- a/releasenotes/notes/labels-6d0096c7d8505627.yaml +++ b/releasenotes/notes/labels-6d0096c7d8505627.yaml @@ -9,3 +9,10 @@ features: Labels can have an optional description attached, which will provide a little insight into the purpose of the label. Labels are completely customizable and the labels available will vary by instance. +api: + - | + The ``/patches`` endpoint now exposes a ``labels`` attribute for each + patch. + - | + The ``/covers`` endpoint now exposes a ``labels`` attribute for each cover + letter. -- 2.17.1 _______________________________________________ Patchwork mailing list Patchwork@lists.ozlabs.org https://lists.ozlabs.org/listinfo/patchwork