[v2,09/10] REST: Expose Patch.labels

Message ID 20181014124541.13393-10-stephen@that.guru
State New
Headers show
Series
  • Add labels support
Related show

Commit Message

Stephen Finucane Oct. 14, 2018, 12:45 p.m.
This closes out the labels feature.

Signed-off-by: Stephen Finucane <stephen@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(-)

Patch

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='test@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='test@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.