Skip to content

Commit

Permalink
rest: add api/v1/messages endpoint (POST support)
Browse files Browse the repository at this point in the history
The endpoint applies a single message to the project according to the user
that is sending the request and the groups it belongs. If the user is an
importer, the message is added to all recognised projects. For other
users, the message is added to all the projects that are recognised and
maintained by the user.

Message-Id: <20180515120228.47478-1-shubhamjain7495@gmail.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
  • Loading branch information
shubhamdotjain authored and bonzini committed May 15, 2018
1 parent 9093169 commit 2002313
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 6 deletions.
28 changes: 24 additions & 4 deletions api/rest.py
Expand Up @@ -17,7 +17,7 @@
from .models import Project, Message
from .search import SearchEngine
from rest_framework import (permissions, serializers, viewsets, filters,
mixins, generics, renderers)
mixins, generics, renderers, status)
from rest_framework.decorators import detail_route
from rest_framework.fields import SerializerMethodField, CharField, JSONField, EmailField
from rest_framework.relations import HyperlinkedIdentityField
Expand Down Expand Up @@ -162,7 +162,6 @@ def create(self, validated_data):
except:
return [validated_data['address'], validated_data['address']]


class BaseMessageSerializer(serializers.ModelSerializer):
class Meta:
model = Message
Expand All @@ -175,6 +174,9 @@ class Meta:
def create(self, validated_data):
validated_data['recipients'] = self.fields['recipients'].create(validated_data['recipients'])
validated_data['sender'] = self.fields['sender'].create(validated_data['sender'])
if 'project' in validated_data:
project = validated_data.pop('project')
return Message.objects.create(project=project, **validated_data)
return Message.objects.create(project=self.context['project'], **validated_data)

# a message_id is *not* unique, so we can only list
Expand Down Expand Up @@ -288,7 +290,6 @@ class SeriesViewSet(BaseMessageViewSet):
filter_backends = (PatchewSearchFilter,)
search_fields = (SEARCH_PARAM,)


class ProjectSeriesViewSet(ProjectMessagesViewSetMixin,
SeriesViewSet, mixins.DestroyModelMixin):
def collect_patches(self, series):
Expand Down Expand Up @@ -368,7 +369,7 @@ def parse(self, stream, media_type=None, parser_context=None):
data = stream.read().decode("utf-8")
return MboxMessage(data).get_json()

class MessagesViewSet(ProjectMessagesViewSetMixin,
class ProjectMessagesViewSet(ProjectMessagesViewSetMixin,
BaseMessageViewSet, mixins.CreateModelMixin):
serializer_class = MessageSerializer
parser_classes = (JSONParser, MessagePlainTextParser, )
Expand All @@ -388,6 +389,25 @@ def replies(self, request, *args, **kwargs):
context=self.get_serializer_context())
return self.get_paginated_response(serializer.data)

class MessagesViewSet(BaseMessageViewSet):
serializer_class = MessageSerializer
parser_classes = (JSONParser, MessagePlainTextParser, )

def create(self, request, *args, **kwargs):
projects = [p for p in Project.objects.all() if p.recognizes(MboxMessage(self.request.data['mbox']))]
if 'importers' not in self.request.user.groups.all():
projects = (p for p in projects if p.maintained_by(self.request.user))
results = []
for project in projects:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(project=project)
results.append(serializer.data)
# Fake paginator response. Note that there is no Location header.
return Response(OrderedDict([('count', len(results)),
('results', results)]),
status=status.HTTP_201_CREATED)

# Results

class HyperlinkedResultField(HyperlinkedIdentityField):
Expand Down
3 changes: 2 additions & 1 deletion api/urls.py
Expand Up @@ -31,12 +31,13 @@ def _build_urls(base=None, r=[]):
router.register('users', rest.UsersViewSet)
router.register('projects', rest.ProjectsViewSet)
router.register('series', rest.SeriesViewSet, base_name='series')
router.register('messages', rest.MessagesViewSet)

projects_router = NestedDefaultRouter(router, 'projects', lookup='projects', trailing_slash=True)
projects_router.include_format_suffixes = False
projects_router.register('results', rest.ProjectResultsViewSet, base_name='results')
projects_router.register('series', rest.ProjectSeriesViewSet, base_name='series')
projects_router.register('messages', rest.MessagesViewSet, base_name='messages')
projects_router.register('messages', rest.ProjectMessagesViewSet, base_name='messages')

results_router = NestedDefaultRouter(projects_router, 'series', lookup='series', trailing_slash=True)
results_router.include_format_suffixes = False
Expand Down
Binary file added tests/data/0023-multiple-project-patch.mbox.gz
Binary file not shown.
Binary file added tests/data/0024-multiple-project-patch.json.gz
Binary file not shown.
32 changes: 31 additions & 1 deletion tests/test_rest.py
Expand Up @@ -37,6 +37,8 @@ def setUp(self):
self.sp.prefix_tags = "block"
self.sp.save()
self.SUBPROJECT_BASE = '%sprojects/%d/' % (self.REST_BASE, self.sp.id)
self.p2 = self.add_project("EDK 2", "edk2-devel@lists.01.org")
self.PROJECT_BASE_2 = '%sprojects/%d/' % (self.REST_BASE, self.p2.id)

self.admin = User.objects.get(username='admin')
self.USER_BASE = '%susers/%d/' % (self.REST_BASE, self.admin.id)
Expand Down Expand Up @@ -64,7 +66,7 @@ def test_user(self):

def test_projects(self):
resp = self.api_client.get(self.REST_BASE + 'projects/')
self.assertEquals(resp.data['count'], 2)
self.assertEquals(resp.data['count'], 3)
self.assertEquals(resp.data['results'][0]['resource_uri'], self.PROJECT_BASE)
self.assertEquals(resp.data['results'][0]['name'], "QEMU")
self.assertEquals(resp.data['results'][0]['mailing_list'], "qemu-devel@nongnu.org")
Expand Down Expand Up @@ -295,6 +297,34 @@ def test_create_text_message(self):
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_create_message_without_project_pk(self):
dp = self.get_data_path("0024-multiple-project-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.REST_BASE + "messages/", data, content_type='application/json')
self.assertEqual(resp.status_code, 201)
self.assertEqual(resp.data['count'], 2)
resp_get = self.api_client.get(self.PROJECT_BASE + "messages/20180223132311.26555-2-marcandre.lureau@redhat.com/")
self.assertEqual(resp_get.status_code, 200)
self.assertEqual(resp_get.data['subject'], "[Qemu-devel] [PATCH 1/7] SecurityPkg/Tcg2Pei: drop Tcg2PhysicalPresenceLib dependency")
resp_get2 = self.api_client.get(self.PROJECT_BASE_2 + "messages/20180223132311.26555-2-marcandre.lureau@redhat.com/")
self.assertEqual(resp_get2.status_code, 200)

def test_create_text_message_without_project_pk(self):
dp = self.get_data_path("0023-multiple-project-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.REST_BASE + "messages/", data, content_type='message/rfc822')
self.assertEqual(resp.status_code, 201)
self.assertEqual(resp.data['count'], 2)
resp_get = self.api_client.get(self.PROJECT_BASE + "messages/20180223132311.26555-2-marcandre.lureau@redhat.com/")
self.assertEqual(resp_get.status_code, 200)
self.assertEqual(resp_get.data['subject'], "[Qemu-devel] [PATCH 1/7] SecurityPkg/Tcg2Pei: drop Tcg2PhysicalPresenceLib dependency")
resp_get2 = self.api_client.get(self.PROJECT_BASE_2 + "messages/20180223132311.26555-2-marcandre.lureau@redhat.com/")
self.assertEqual(resp_get2.status_code, 200)

def test_message(self):
series = self.apply_and_retrieve('0001-simple-patch.mbox.gz',
self.p.id, '20160628014747.20971-1-famz@redhat.com')
Expand Down

0 comments on commit 2002313

Please sign in to comment.