[PATCH v2 09/10] REST: Expose Patch.labels
Stephen Finucane
stephen at that.guru
Sun Oct 14 23:45:40 AEDT 2018
This closes out the labels feature.
Signed-off-by: Stephen Finucane <stephen at 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='test at 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 at 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
More information about the Patchwork
mailing list