[Patchew-devel] [PATCH 01/11] rest: add PATCH support for series and messages

Paolo Bonzini pbonzini at redhat.com
Wed May 15 12:57:58 UTC 2019


Allow modifying those fields that are not directly derived from the
mbox.  The complication is that PATCH and POST now support a different
set of fields (POST allows writing arbitrary data to the message) so
we need to use get_serializer_class() instead of the simpler
serializer_class.

Signed-off-by: Paolo Bonzini <pbonzini at redhat.com>
---
 api/rest.py        | 52 +++++++++++++++++++++++++++++++++-------------
 tests/test_rest.py | 32 ++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+), 14 deletions(-)

diff --git a/api/rest.py b/api/rest.py
index 1bd637d..3b6ab2f 100644
--- a/api/rest.py
+++ b/api/rest.py
@@ -22,9 +22,10 @@ from rest_framework.decorators import action
 from rest_framework.fields import SerializerMethodField, CharField, JSONField, EmailField, ListField
 from rest_framework.relations import HyperlinkedIdentityField
 from rest_framework.response import Response
+from rest_framework.views import APIView
 import rest_framework
 from mbox import addr_db_to_rest, MboxMessage
-from rest_framework.parsers import JSONParser, BaseParser
+from rest_framework.parsers import BaseParser
 
 SEARCH_PARAM = 'q'
 
@@ -274,7 +275,8 @@ class AddressSerializer(serializers.Serializer):
 class BaseMessageSerializer(serializers.ModelSerializer):
     class Meta:
         model = Message
-        fields = ('resource_uri', 'message_id', 'subject', 'date', 'sender', 'recipients', 'tags')
+        read_only_fields = ('resource_uri', 'message_id', 'subject', 'date', 'sender', 'recipients')
+        fields = read_only_fields + ('tags', )
 
     resource_uri = HyperlinkedMessageField(view_name='messages-detail')
     recipients = AddressSerializer(many=True)
@@ -300,7 +302,7 @@ class BaseMessageViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
 
 
 # a (project, message_id) tuple is unique, so we can always retrieve an object
-class ProjectMessagesViewSetMixin(mixins.RetrieveModelMixin):
+class ProjectMessagesViewSetMixin(mixins.RetrieveModelMixin, mixins.UpdateModelMixin):
     def get_queryset(self):
         return self.queryset.filter(project=self.kwargs['projects_pk'])
 
@@ -342,10 +344,12 @@ class PatchSerializer(BaseMessageSerializer):
 class SeriesSerializer(BaseMessageSerializer):
     class Meta:
         model = Message
-        fields = ('resource_uri',) + BaseMessageSerializer.Meta.fields + (
-            'message', 'stripped_subject', 'last_comment_date', 'last_reply_date',
-            'is_complete', 'is_merged', 'num_patches', 'total_patches', 'results',
+        subclass_read_only_fields = ('message', 'stripped_subject', 'num_patches',
+            'total_patches', 'results')
+        fields = BaseMessageSerializer.Meta.fields + subclass_read_only_fields + (
+            'last_comment_date', 'last_reply_date', 'is_complete', 'is_merged',
             'is_obsolete', 'is_tested', 'is_reviewed', 'maintainers')
+        read_only_fields = BaseMessageSerializer.Meta.read_only_fields + subclass_read_only_fields
 
     resource_uri = HyperlinkedMessageField(view_name='series-detail')
     message = HyperlinkedMessageField(view_name='messages-detail')
@@ -416,7 +420,8 @@ class SeriesViewSet(BaseMessageViewSet):
 
 
 class ProjectSeriesViewSet(ProjectMessagesViewSetMixin,
-                           SeriesViewSet, mixins.DestroyModelMixin):
+                           SeriesViewSet, mixins.UpdateModelMixin,
+                           mixins.DestroyModelMixin):
     def collect_patches(self, series):
         if series.is_patch:
             patches = [series]
@@ -456,11 +461,12 @@ class ProjectSeriesViewSet(ProjectMessagesViewSetMixin,
 
 # Messages
 
-# TODO: add POST endpoint connected to email plugin?
 class MessageSerializer(BaseMessageSerializer):
     class Meta:
         model = Message
         fields = BaseMessageSerializer.Meta.fields + ('mbox', )
+        read_only_fields = BaseMessageSerializer.Meta.read_only_fields + ('mbox', )
+
     mbox = CharField()
 
     def get_fields(self):
@@ -476,6 +482,14 @@ class MessageSerializer(BaseMessageSerializer):
         return fields
 
 
+class MessageCreationSerializer(BaseMessageSerializer):
+    class Meta:
+        model = Message
+        fields = BaseMessageSerializer.Meta.fields + ('mbox', )
+        read_only_fields = []
+
+    mbox = CharField()
+
 class StaticTextRenderer(renderers.BaseRenderer):
     media_type = 'text/plain'
     format = 'mbox'
@@ -497,10 +511,15 @@ class MessagePlainTextParser(BaseParser):
         return MboxMessage(data).get_json()
 
 
-class ProjectMessagesViewSet(ProjectMessagesViewSetMixin,
-                             BaseMessageViewSet, mixins.CreateModelMixin):
-    serializer_class = MessageSerializer
-    parser_classes = (JSONParser, MessagePlainTextParser, )
+class ProjectMessagesViewSet(ProjectMessagesViewSetMixin, BaseMessageViewSet,
+                             mixins.CreateModelMixin, mixins.UpdateModelMixin):
+    parser_classes = APIView.parser_classes + [MessagePlainTextParser]
+
+    def get_serializer_class(self, *args, **kwargs):
+        if self.request.method == 'POST':
+            return MessageCreationSerializer
+        else:
+            return MessageSerializer
 
     @action(detail=True, renderer_classes=[StaticTextRenderer])
     def mbox(self, request, *args, **kwargs):
@@ -519,9 +538,14 @@ class ProjectMessagesViewSet(ProjectMessagesViewSetMixin,
 
 
 class MessagesViewSet(BaseMessageViewSet):
-    serializer_class = MessageSerializer
     permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
-    parser_classes = (JSONParser, MessagePlainTextParser, )
+    parser_classes = APIView.parser_classes + [MessagePlainTextParser]
+
+    def get_serializer_class(self, *args, **kwargs):
+        if self.request.method == 'POST':
+            return MessageCreationSerializer
+        else:
+            return MessageSerializer
 
     def create(self, request, *args, **kwargs):
         m = MboxMessage(request.data['mbox'])
diff --git a/tests/test_rest.py b/tests/test_rest.py
index dd38035..d6cbf62 100755
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -353,6 +353,24 @@ class RestTest(PatchewTestCase):
         self.assertEqual(resp.data['subject'], "[Qemu-devel] [PATCH v2 10/27] imx_fec: Reserve full 4K "
                          "page for the register file")
 
+    def test_patch_message(self):
+        the_tags = ['Reviewed-by: Paolo Bonzini <pbonzini at redhat.com']
+        dp = self.get_data_path("0022-another-simple-patch.json.gz")
+        with open(dp, "r") as f:
+            data = f.read()
+        self.api_client.login(username=self.user, password=self.password)
+        resp = self.api_client.post(self.PROJECT_BASE + "messages/", data, content_type='application/json')
+        self.assertEqual(resp.status_code, 201)
+        resp_get = self.api_client.get(self.PROJECT_BASE + "messages/20171023201055.21973-11-andrew.smirnov at gmail.com/")
+        self.assertEqual(resp_get.status_code, 200)
+        self.assertEqual(resp_get.data['tags'], [])
+        resp = self.api_client.patch(self.PROJECT_BASE + "messages/20171023201055.21973-11-andrew.smirnov at gmail.com/",
+                                     { 'tags': the_tags })
+        self.assertEqual(resp.status_code, 200)
+        resp_get = self.api_client.get(self.PROJECT_BASE + "messages/20171023201055.21973-11-andrew.smirnov at gmail.com/")
+        self.assertEqual(resp_get.status_code, 200)
+        self.assertEqual(resp_get.data['tags'], the_tags)
+
     def test_create_text_message(self):
         dp = self.get_data_path("0004-multiple-patch-reviewed.mbox.gz")
         with open(dp, "r") as f:
@@ -364,6 +382,20 @@ class RestTest(PatchewTestCase):
         self.assertEqual(resp_get.status_code, 200)
         self.assertEqual(resp.data['subject'], "[Qemu-devel] [PATCH v4 0/2] Report format specific info for LUKS block driver")
 
+    def test_patch_series(self):
+        dp = self.get_data_path("0001-simple-patch.mbox.gz")
+        with open(dp, "r") as f:
+            data = f.read()
+        self.api_client.login(username=self.user, password=self.password)
+        resp = self.api_client.post(self.PROJECT_BASE + "messages/", data, content_type='message/rfc822')
+        self.assertEqual(resp.status_code, 201)
+        resp = self.api_client.patch(self.PROJECT_BASE + "series/20160628014747.20971-1-famz at redhat.com/",
+                                     { 'is_tested' : True })
+        self.assertEqual(resp.status_code, 200)
+        resp_get = self.api_client.get(self.PROJECT_BASE + "series/20160628014747.20971-1-famz at redhat.com/")
+        self.assertEqual(resp_get.status_code, 200)
+        self.assertTrue(resp_get.data['is_tested'])
+
     def test_create_message_without_project_pk(self):
         dp = self.get_data_path("0024-multiple-project-patch.json.gz")
         with open(dp, "r") as f:
-- 
2.21.0





More information about the Patchew-devel mailing list