From patchwork Wed Apr 11 17:45:12 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Veronika Kabatova X-Patchwork-Id: 897328 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 ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 40Lrzj5jr2z9s3L for ; Thu, 12 Apr 2018 03:45:37 +1000 (AEST) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 40Lrzj4YXbzF0QZ for ; Thu, 12 Apr 2018 03:45:37 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dmarc=pass (p=none dis=none) header.from=redhat.com X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=redhat.com (client-ip=66.187.233.73; helo=mx1.redhat.com; envelope-from=vkabatov@redhat.com; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=pass (p=none dis=none) header.from=redhat.com Received: from mx1.redhat.com (mx3-rdu2.redhat.com [66.187.233.73]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 40Lrzd2gStzDrJw for ; Thu, 12 Apr 2018 03:45:32 +1000 (AEST) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 87E364023112 for ; Wed, 11 Apr 2018 17:45:29 +0000 (UTC) Received: from vkabatova.usersys.redhat.com (unknown [10.43.17.191]) by smtp.corp.redhat.com (Postfix) with ESMTP id 157A6215CDAF; Wed, 11 Apr 2018 17:45:28 +0000 (UTC) From: vkabatov@redhat.com To: patchwork@lists.ozlabs.org Subject: [PATCH v2 1/2] api: Add comments to patch and cover endpoints Date: Wed, 11 Apr 2018 19:45:12 +0200 Message-Id: <20180411174513.25938-1-vkabatov@redhat.com> X-Scanned-By: MIMEDefang 2.78 on 10.11.54.6 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.6]); Wed, 11 Apr 2018 17:45:29 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.6]); Wed, 11 Apr 2018 17:45:29 +0000 (UTC) for IP:'10.11.54.6' DOMAIN:'int-mx06.intmail.prod.int.rdu2.redhat.com' HELO:'smtp.corp.redhat.com' FROM:'vkabatov@redhat.com' RCPT:'' X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.26 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" From: Veronika Kabatova Signed-off-by: Veronika Kabatova --- patchwork/api/cover.py | 14 ++++++-- patchwork/api/embedded.py | 38 ++++++++++++++++++++++ patchwork/api/patch.py | 15 ++++++--- patchwork/models.py | 3 ++ .../notes/comments-api-b7dff6ee4ce04c9b.yaml | 8 +++++ 5 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml diff --git a/patchwork/api/cover.py b/patchwork/api/cover.py index fc7ae97..5a6e377 100644 --- a/patchwork/api/cover.py +++ b/patchwork/api/cover.py @@ -25,6 +25,7 @@ from rest_framework.serializers import SerializerMethodField from patchwork.api.base import BaseHyperlinkedModelSerializer from patchwork.api.filters import CoverLetterFilter +from patchwork.api.embedded import CommentSerializer from patchwork.api.embedded import PersonSerializer from patchwork.api.embedded import ProjectSerializer from patchwork.api.embedded import SeriesSerializer @@ -58,5 +59,6 @@ class CoverLetterListSerializer(BaseHyperlinkedModelSerializer): class CoverLetterDetailSerializer(CoverLetterListSerializer): headers = SerializerMethodField() + comments = CommentSerializer(many=True, read_only=True) def get_headers(self, instance): @@ -65,9 +67,14 @@ class CoverLetterDetailSerializer(CoverLetterListSerializer): class Meta: model = CoverLetter - fields = CoverLetterListSerializer.Meta.fields + ('headers', 'content') + fields = CoverLetterListSerializer.Meta.fields + ('headers', + 'content', + 'comments') read_only_fields = fields extra_kwargs = CoverLetterListSerializer.Meta.extra_kwargs + versioned_fields = { + '1.1': ('mbox', 'comments'), + } class CoverLetterList(ListAPIView): @@ -91,5 +98,6 @@ class CoverLetterDetail(RetrieveAPIView): serializer_class = CoverLetterDetailSerializer def get_queryset(self): - return CoverLetter.objects.all().prefetch_related('series')\ - .select_related('project', 'submitter') + return CoverLetter.objects.all().prefetch_related( + 'series', 'comments' + ).select_related('project', 'submitter') diff --git a/patchwork/api/embedded.py b/patchwork/api/embedded.py index d79724c..afd511a 100644 --- a/patchwork/api/embedded.py +++ b/patchwork/api/embedded.py @@ -23,6 +23,8 @@ A collection of serializers. None of the serializers here should reference nested fields. """ +import email.parser + from rest_framework.serializers import CharField from rest_framework.serializers import SerializerMethodField @@ -163,3 +165,39 @@ class UserProfileSerializer(BaseHyperlinkedModelSerializer): extra_kwargs = { 'url': {'view_name': 'api-user-detail'}, } + + +class CommentSerializer(BaseHyperlinkedModelSerializer): + + tags = SerializerMethodField() + subject = SerializerMethodField() + headers = SerializerMethodField() + submitter = PersonSerializer(read_only=True) + + def get_tags(self, comment): + # TODO implement after we get support for tags on comments + return {} + + def get_subject(self, comment): + return email.parser.Parser().parsestr(comment.headers, + True).get('Subject', '') + + def get_headers(self, comment): + headers = {} + + if comment.headers: + parsed = email.parser.Parser().parsestr(comment.headers, True) + for key in parsed.keys(): + headers[key] = parsed.get_all(key) + # Let's return a single string instead of a list if only one + # header with this key is present + if len(headers[key]) == 1: + headers[key] = headers[key][0] + + return headers + + class Meta: + model = models.Comment + fields = ('id', 'msgid', 'date', 'subject', 'submitter', 'tags', + 'content', 'headers') + read_only_fields = fields diff --git a/patchwork/api/patch.py b/patchwork/api/patch.py index 115feff..5affa87 100644 --- a/patchwork/api/patch.py +++ b/patchwork/api/patch.py @@ -24,11 +24,12 @@ from rest_framework.generics import ListAPIView from rest_framework.generics import RetrieveUpdateAPIView from rest_framework.relations import RelatedField from rest_framework.reverse import reverse -from rest_framework.serializers import HyperlinkedModelSerializer from rest_framework.serializers import SerializerMethodField +from patchwork.api.base import BaseHyperlinkedModelSerializer from patchwork.api.base import PatchworkPermission from patchwork.api.filters import PatchFilter +from patchwork.api.embedded import CommentSerializer from patchwork.api.embedded import PersonSerializer from patchwork.api.embedded import ProjectSerializer from patchwork.api.embedded import SeriesSerializer @@ -75,7 +76,7 @@ class StateField(RelatedField): return State.objects.all() -class PatchListSerializer(HyperlinkedModelSerializer): +class PatchListSerializer(BaseHyperlinkedModelSerializer): project = ProjectSerializer(read_only=True) state = StateField() @@ -121,5 +122,6 @@ class PatchDetailSerializer(PatchListSerializer): headers = SerializerMethodField() prefixes = SerializerMethodField() + comments = CommentSerializer(many=True, read_only=True) def get_headers(self, patch): @@ -132,10 +134,13 @@ class PatchDetailSerializer(PatchListSerializer): class Meta: model = Patch fields = PatchListSerializer.Meta.fields + ( - 'headers', 'content', 'diff', 'prefixes') + 'headers', 'content', 'diff', 'prefixes', 'comments') read_only_fields = PatchListSerializer.Meta.read_only_fields + ( - 'headers', 'content', 'diff', 'prefixes') + 'headers', 'content', 'diff', 'prefixes', 'comments') extra_kwargs = PatchListSerializer.Meta.extra_kwargs + versioned_fields = { + '1.1': ('comments', ), + } class PatchList(ListAPIView): @@ -164,5 +169,5 @@ class PatchDetail(RetrieveUpdateAPIView): def get_queryset(self): return Patch.objects.all()\ - .prefetch_related('series', 'check_set')\ + .prefetch_related('series', 'check_set', 'comments')\ .select_related('project', 'state', 'submitter', 'delegate') diff --git a/patchwork/models.py b/patchwork/models.py index f91b994..67c2d3a 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -613,6 +613,9 @@ class Comment(EmailMixin, models.Model): if hasattr(self.submission, 'patch'): self.submission.patch.refresh_tag_counts() + def is_editable(self, user): + return False + class Meta: ordering = ['date'] unique_together = [('msgid', 'submission')] diff --git a/releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml b/releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml new file mode 100644 index 0000000..d696aab --- /dev/null +++ b/releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml @@ -0,0 +1,8 @@ +--- +api: + - | + Comments are now exposed when checking patch and cover letter details + (``/patches/{patchID}`` and ``/covers/{coverID}`` endpoints). Fields `id`, + `msgid`, `date`, `subject`, `submitter`, `tags`, `content` and `headers` + are available for each comment associated with the parent submission. + Please note that comments are available only since API version 1.1 From patchwork Wed Apr 11 17:45:13 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Veronika Kabatova X-Patchwork-Id: 897329 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 40Ls017481z9s3L for ; Thu, 12 Apr 2018 03:45:53 +1000 (AEST) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 40Ls015wKszF1Qh for ; Thu, 12 Apr 2018 03:45:53 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dmarc=pass (p=none dis=none) header.from=redhat.com X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=redhat.com (client-ip=66.187.233.73; helo=mx1.redhat.com; envelope-from=vkabatov@redhat.com; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=pass (p=none dis=none) header.from=redhat.com Received: from mx1.redhat.com (mx3-rdu2.redhat.com [66.187.233.73]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 40Lrzk4q3TzF1QH for ; Thu, 12 Apr 2018 03:45:38 +1000 (AEST) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 7BBB276FBA for ; Wed, 11 Apr 2018 17:45:36 +0000 (UTC) Received: from vkabatova.usersys.redhat.com (unknown [10.43.17.191]) by smtp.corp.redhat.com (Postfix) with ESMTP id 1EEAE215CDAF; Wed, 11 Apr 2018 17:45:36 +0000 (UTC) From: vkabatov@redhat.com To: patchwork@lists.ozlabs.org Subject: [PATCH v2 2/2] tests/api: Test comments Date: Wed, 11 Apr 2018 19:45:13 +0200 Message-Id: <20180411174513.25938-2-vkabatov@redhat.com> In-Reply-To: <20180411174513.25938-1-vkabatov@redhat.com> References: <20180411174513.25938-1-vkabatov@redhat.com> X-Scanned-By: MIMEDefang 2.78 on 10.11.54.6 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.1]); Wed, 11 Apr 2018 17:45:36 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.1]); Wed, 11 Apr 2018 17:45:36 +0000 (UTC) for IP:'10.11.54.6' DOMAIN:'int-mx06.intmail.prod.int.rdu2.redhat.com' HELO:'smtp.corp.redhat.com' FROM:'vkabatov@redhat.com' RCPT:'' X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.26 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" From: Veronika Kabatova Check that comments field is added to patch / cover letter details with API version 1.1. This required minor change to api_url() method to avoid errors in case both the 'item' and 'version' parameters are passed (can't use both *args and **kwargs with reverse()). Signed-off-by: Veronika Kabatova --- patchwork/tests/api/test_cover.py | 16 +++++++++++++++- patchwork/tests/api/test_patch.py | 24 +++++++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/patchwork/tests/api/test_cover.py b/patchwork/tests/api/test_cover.py index 3135b7e..7375a27 100644 --- a/patchwork/tests/api/test_cover.py +++ b/patchwork/tests/api/test_cover.py @@ -22,11 +22,13 @@ import unittest from django.conf import settings from patchwork.compat import reverse +from patchwork.tests.utils import create_comment from patchwork.tests.utils import create_cover from patchwork.tests.utils import create_maintainer from patchwork.tests.utils import create_person from patchwork.tests.utils import create_project from patchwork.tests.utils import create_user +from patchwork.tests.utils import SAMPLE_CONTENT if settings.ENABLE_REST_API: from rest_framework import status @@ -49,7 +51,8 @@ class TestCoverLetterAPI(APITestCase): if item is None: return reverse('api-cover-list', kwargs=kwargs) - return reverse('api-cover-detail', args=[item], kwargs=kwargs) + kwargs['pk'] = item + return reverse('api-cover-detail', kwargs=kwargs) def assertSerialized(self, cover_obj, cover_json): self.assertEqual(cover_obj.id, cover_json['id']) @@ -115,6 +118,17 @@ class TestCoverLetterAPI(APITestCase): self.assertEqual(status.HTTP_200_OK, resp.status_code) self.assertSerialized(cover_obj, resp.data) + # test comments + comment_obj = create_comment(submission=cover_obj) + resp = self.client.get(self.api_url(cover_obj.id)) + self.assertEqual(1, len(resp.data['comments'])) + self.assertEqual(comment_obj.id, resp.data['comments'][0]['id']) + self.assertEqual(SAMPLE_CONTENT, resp.data['comments'][0]['content']) + + # test old version of API + resp = self.client.get(self.api_url(cover_obj.id, version='1.0')) + self.assertNotIn('comments', 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 909c1eb..57a269c 100644 --- a/patchwork/tests/api/test_patch.py +++ b/patchwork/tests/api/test_patch.py @@ -24,12 +24,14 @@ from django.conf import settings from patchwork.compat import reverse from patchwork.models import Patch +from patchwork.tests.utils import create_comment from patchwork.tests.utils import create_maintainer from patchwork.tests.utils import create_patch from patchwork.tests.utils import create_person from patchwork.tests.utils import create_project from patchwork.tests.utils import create_state from patchwork.tests.utils import create_user +from patchwork.tests.utils import SAMPLE_CONTENT if settings.ENABLE_REST_API: from rest_framework import status @@ -45,10 +47,15 @@ class TestPatchAPI(APITestCase): fixtures = ['default_tags'] @staticmethod - def api_url(item=None): + def api_url(item=None, version=None): + kwargs = {} + if version: + kwargs['version'] = version + if item is None: - return reverse('api-patch-list') - return reverse('api-patch-detail', args=[item]) + return reverse('api-patch-list', kwargs=kwargs) + kwargs['pk'] = item + return reverse('api-patch-detail', kwargs=kwargs) def assertSerialized(self, patch_obj, patch_json): self.assertEqual(patch_obj.id, patch_json['id']) @@ -130,6 +137,17 @@ class TestPatchAPI(APITestCase): self.assertEqual(patch.diff, resp.data['diff']) self.assertEqual(0, len(resp.data['tags'])) + # test comments + comment = create_comment(submission=patch) + resp = self.client.get(self.api_url(patch.id)) + self.assertEqual(1, len(resp.data['comments'])) + self.assertEqual(comment.id, resp.data['comments'][0]['id']) + self.assertEqual(SAMPLE_CONTENT, resp.data['comments'][0]['content']) + + # test old version of API + resp = self.client.get(self.api_url(item=patch.id, version='1.0')) + self.assertNotIn('comments', resp.data) + def test_create(self): """Ensure creations are rejected.""" project = create_project()