From patchwork Sun Oct 14 12:45:40 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 983723 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [203.11.71.2]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 42Y1dS1983z9s8F for ; Sun, 14 Oct 2018 23:50:36 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=that.guru Authentication-Results: ozlabs.org; dkim=fail reason="key not found in DNS" (0-bit key; unprotected) header.d=that.guru header.i=@that.guru header.b="ELSFKNFh"; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 42Y1dR6YQBzDrSM for ; Sun, 14 Oct 2018 23:50:35 +1100 (AEDT) Authentication-Results: lists.ozlabs.org; dmarc=none (p=none dis=none) header.from=that.guru Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" (0-bit key; unprotected) header.d=that.guru header.i=@that.guru header.b="ELSFKNFh"; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (mailfrom) smtp.mailfrom=that.guru (client-ip=185.234.75.13; helo=relay013.mxrelay.co; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=none (p=none dis=none) header.from=that.guru Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" (0-bit key; unprotected) header.d=that.guru header.i=@that.guru header.b="ELSFKNFh"; dkim-atps=neutral Received: from relay013.mxrelay.co (relay013.mxrelay.co [185.234.75.13]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 42Y1YT5Pr1zF31S for ; Sun, 14 Oct 2018 23:47:09 +1100 (AEDT) Received: from filter002.mxroute.com (unknown [185.133.192.179]) by relay013.mxrelay.co (Postfix) with ESMTP id 6910042E0D; Sun, 14 Oct 2018 12:46:37 +0000 (UTC) Received: from one.mxroute.com (one.mxroute.com [195.201.59.211]) by filter002.mxroute.com (Postfix) with ESMTPS id 4296F3F051; Sun, 14 Oct 2018 12:46:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=default; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From: Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=no5NbKUXSu1OJxej9Oct0WSGGJxAXAoz6oWdrkTrha4=; b=ELSFKNFhPzI0wIHyXevlpcFbPE gAXlA0KBpzDOY/m90FqEN8k3rP7SajEf3Y8MEhkNbOxIYgNd+uZ/lP+IPMppgZtvpVknJEbKfOf8G VUlaDVjWXooDvhMD0yOY8lm5mf+d4Ypi98gyIpxCwrtwAMrxAnOy+ql2Lk+NwWm4w369pECg0Mw89 OCp09RhmQHVbPlRkfk+ObkiPsKsf+Dp9BivBpWqU4LMdy81eAw0WhEK7b9AVlWr22bNd6IeujZsxA J/S8TKLlp4PT+tOk7PsaVdG0MbovYqobyOC6wpKOR6JE8G7uYbKtP/yudoBjD4wXRSOoMWfcJe96r ygZAQqMQ==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH v2 09/10] REST: Expose Patch.labels Date: Sun, 14 Oct 2018 13:45:40 +0100 Message-Id: <20181014124541.13393-10-stephen@that.guru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181014124541.13393-1-stephen@that.guru> References: <20181014124541.13393-1-stephen@that.guru> X-AuthUser: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: mpe@ellerman.id.au, andrew.donnellan@au1.ibm.com MIME-Version: 1.0 Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" This closes out the labels feature. Signed-off-by: Stephen Finucane 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@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.