From patchwork Fri Mar 23 12:33:38 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Veronika Kabatova X-Patchwork-Id: 889972 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 40733k0xQyz9s0n for ; Fri, 23 Mar 2018 23:38:10 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from bilbo.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 40733h6gGfzF1V4 for ; Fri, 23 Mar 2018 23:38:08 +1100 (AEDT) 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 4072yn2WjJzF0wk for ; Fri, 23 Mar 2018 23:33:52 +1100 (AEDT) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 6022EBD87 for ; Fri, 23 Mar 2018 12:33:49 +0000 (UTC) Received: from vkabatova.usersys.redhat.com (unknown [10.43.17.106]) by smtp.corp.redhat.com (Postfix) with ESMTP id 031EF10B2B2B; Fri, 23 Mar 2018 12:33:48 +0000 (UTC) From: vkabatov@redhat.com To: patchwork@lists.ozlabs.org Subject: [PATCH] Add comments REST API Date: Fri, 23 Mar 2018 13:33:38 +0100 Message-Id: <20180323123338.17509-1-vkabatov@redhat.com> X-Scanned-By: MIMEDefang 2.78 on 10.11.54.3 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.1]); Fri, 23 Mar 2018 12:33:49 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.1]); Fri, 23 Mar 2018 12:33:49 +0000 (UTC) for IP:'10.11.54.3' DOMAIN:'int-mx03.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 --- docs/api/rest.rst | 6 +- patchwork/api/comment.py | 117 +++++++++++++++++++++ patchwork/api/filters.py | 16 +++ patchwork/api/index.py | 1 + patchwork/models.py | 3 + patchwork/urls.py | 10 ++ .../notes/comments-api-b7dff6ee4ce04c9b.yaml | 10 ++ 7 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 patchwork/api/comment.py create mode 100644 releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml diff --git a/docs/api/rest.rst b/docs/api/rest.rst index d526b27..e33aefc 100644 --- a/docs/api/rest.rst +++ b/docs/api/rest.rst @@ -48,7 +48,8 @@ Patchwork instance hosted at `patchwork.example.com`, run: "people": "https://patchwork.example.com/api/1.0/people/", "projects": "https://patchwork.example.com/api/1.0/projects/", "series": "https://patchwork.example.com/api/1.0/series/", - "users": "https://patchwork.example.com/api/1.0/users/" + "users": "https://patchwork.example.com/api/1.0/users/", + "comments": "https://patchwork.example.com/api/1.0/comments/" } @@ -71,7 +72,8 @@ well-supported. To repeat the above example using `requests`:, run "people": "https://patchwork.example.com/api/1.0/people/", "projects": "https://patchwork.example.com/api/1.0/projects/", "series": "https://patchwork.example.com/api/1.0/series/", - "users": "https://patchwork.example.com/api/1.0/users/" + "users": "https://patchwork.example.com/api/1.0/users/", + "comments": "https://patchwork.example.com/api/1.0/comments/" } Tools like `curl` and libraries like `requests` can be used to build anything diff --git a/patchwork/api/comment.py b/patchwork/api/comment.py new file mode 100644 index 0000000..4252ecd --- /dev/null +++ b/patchwork/api/comment.py @@ -0,0 +1,117 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2018 Red Hat +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import email.parser + +from rest_framework.generics import ListAPIView +from rest_framework.generics import RetrieveUpdateAPIView +from rest_framework.reverse import reverse +from rest_framework.serializers import HyperlinkedModelSerializer +from rest_framework.serializers import SerializerMethodField + +from patchwork.api.base import PatchworkPermission +from patchwork.api.filters import CommentFilter +from patchwork.api.embedded import PersonSerializer +from patchwork.api.embedded import ProjectSerializer +from patchwork.models import Comment + + +class CommentListSerializer(HyperlinkedModelSerializer): + + submitter = PersonSerializer(read_only=True) + tags = SerializerMethodField() + subject = SerializerMethodField() + parent = SerializerMethodField(source='submission') + + def get_parent(self, instance): + attrs = {'subject': instance.submission.name, + 'msgid': instance.submission.msgid, + 'date': instance.submission.date} + + if hasattr(instance.submission, 'patch'): + attrs['url'] = self.context.get('request').build_absolute_uri( + reverse('api-patch-detail', args=[instance.submission.id])) + else: + attrs['url'] = self.context.get('request').build_absolute_uri( + reverse('api-cover-detail', args=[instance.submission.id])) + + return attrs + + def get_subject(self, instance): + return email.parser.Parser().parsestr(instance.headers, + True).get('Subject', '') + + def get_tags(self, instance): + # TODO implement after we get support for tags on comments + return {} + + class Meta: + model = Comment + fields = ('id', 'url', 'msgid', 'date', 'subject', 'submitter', + 'parent', 'tags') + read_only_fields = fields + extra_kwargs = { + 'url': {'view_name': 'api-comment-detail'}, + } + + +class CommentDetailSerializer(CommentListSerializer): + + headers = SerializerMethodField() + project = ProjectSerializer(source='submission.project', read_only=True) + + def get_headers(self, comment): + if comment.headers: + return email.parser.Parser().parsestr(comment.headers, True) + + class Meta: + model = Comment + fields = CommentListSerializer.Meta.fields + ( + 'content', 'headers', 'project' + ) + read_only_fields = fields + extra_kwargs = CommentListSerializer.Meta.extra_kwargs + + +class CommentList(ListAPIView): + """List comments""" + + permission_classes = (PatchworkPermission,) + serializer_class = CommentListSerializer + filter_class = CommentFilter + search_fields = ('subject',) + ordering_fields = ('id', 'subject', 'date', 'submitter') + ordering = 'id' + + def get_queryset(self): + return Comment.objects.all().select_related( + 'submission' + ).prefetch_related('related_tags').defer('content') + + +class CommentDetail(RetrieveUpdateAPIView): + """Show a comment""" + + permission_classes = (PatchworkPermission,) + serializer_class = CommentDetailSerializer + + def get_queryset(self): + return Comment.objects.all().select_related( + 'submission' + ).prefetch_related('related_tags') diff --git a/patchwork/api/filters.py b/patchwork/api/filters.py index d207b2b..8256d8c 100644 --- a/patchwork/api/filters.py +++ b/patchwork/api/filters.py @@ -26,6 +26,7 @@ from django.forms import ModelChoiceField from patchwork.models import Bundle from patchwork.models import Check +from patchwork.models import Comment from patchwork.models import CoverLetter from patchwork.models import Event from patchwork.models import Patch @@ -33,6 +34,7 @@ from patchwork.models import Person from patchwork.models import Project from patchwork.models import Series from patchwork.models import State +from patchwork.models import Submission class TimestampMixin(FilterSet): @@ -186,3 +188,17 @@ class BundleFilter(ProjectMixin, FilterSet): class Meta: model = Bundle fields = ('project', 'owner', 'public') + + +class CommentFilter(ProjectMixin, TimestampMixin, FilterSet): + + submitter = PersonFilter(queryset=Person.objects.all()) + parent = ModelChoiceFilter(name='submission', + queryset=Submission.objects.all()) + project = ProjectFilter(to_field_name='linkname', + name='submission__project', + queryset=Project.objects.all()) + + class Meta: + model = Comment + fields = ('project', 'parent', 'submitter') diff --git a/patchwork/api/index.py b/patchwork/api/index.py index 53494db..ebc8dbf 100644 --- a/patchwork/api/index.py +++ b/patchwork/api/index.py @@ -34,4 +34,5 @@ class IndexView(APIView): 'series': reverse('api-series-list', request=request), 'events': reverse('api-event-list', request=request), 'bundles': reverse('api-bundle-list', request=request), + 'comments': reverse('api-comment-list', request=request), }) 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/patchwork/urls.py b/patchwork/urls.py index 7193472..7ae4425 100644 --- a/patchwork/urls.py +++ b/patchwork/urls.py @@ -213,6 +213,7 @@ if settings.ENABLE_REST_API: from patchwork.api import bundle as api_bundle_views # noqa from patchwork.api import check as api_check_views # noqa + from patchwork.api import comment as api_comment_views # noqa from patchwork.api import cover as api_cover_views # noqa from patchwork.api import event as api_event_views # noqa from patchwork.api import index as api_index_views # noqa @@ -238,6 +239,12 @@ if settings.ENABLE_REST_API: url(r'^people/(?P[^/]+)/$', api_person_views.PersonDetail.as_view(), name='api-person-detail'), + url(r'^comments/$', + api_comment_views.CommentList.as_view(), + name='api-comment-list'), + url(r'^comments/(?P[^/]+)/$', + api_comment_views.CommentDetail.as_view(), + name='api-comment-detail'), url(r'^covers/$', api_cover_views.CoverLetterList.as_view(), name='api-cover-list'), @@ -277,6 +284,9 @@ if settings.ENABLE_REST_API: url(r'^events/$', api_event_views.EventList.as_view(), name='api-event-list'), + url(r'^comments/$', + api_comment_views.CommentList.as_view(), + name='api-comment-list'), ] urlpatterns += [ diff --git a/releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml b/releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml new file mode 100644 index 0000000..72b3339 --- /dev/null +++ b/releasenotes/notes/comments-api-b7dff6ee4ce04c9b.yaml @@ -0,0 +1,10 @@ +--- +api: + - | + Added /comments endpoint. For comment list, fields + 'id', 'url', 'msgid', 'date', 'subject', 'submitter', 'parent', 'tags' + are available. For comment detail (/comments/29), comment's + 'content', 'headers', 'project' + are added as well. Filtering is possible based on + 'project', 'parent', 'submitter' + fields.