[PATCH v2 6/6] REST: Expose events
Stephen Finucane
stephen at that.guru
Tue Jan 31 10:04:14 AEDT 2017
This is a list only endpoint as it's expected that we would kill events
after a certain duration and would have no reason to allow indexing of
past events.
Signed-off-by: Stephen Finucane <stephen at that.guru>
---
Changes since v1:
- Rework serializer to handle additional events
---
patchwork/api/base.py | 21 ++++++++--
patchwork/api/check.py | 4 +-
patchwork/api/event.py | 104 +++++++++++++++++++++++++++++++++++++++++++++++
patchwork/api/filters.py | 8 ++++
patchwork/api/index.py | 1 +
patchwork/signals.py | 9 ++--
patchwork/urls.py | 6 ++-
7 files changed, 144 insertions(+), 9 deletions(-)
create mode 100644 patchwork/api/event.py
diff --git a/patchwork/api/base.py b/patchwork/api/base.py
index 13a8432..b742db7 100644
--- a/patchwork/api/base.py
+++ b/patchwork/api/base.py
@@ -20,6 +20,7 @@
from django.conf import settings
from django.shortcuts import get_object_or_404
from rest_framework import permissions
+from rest_framework.pagination import CursorPagination
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
@@ -29,7 +30,7 @@ STATE_CHOICES = ['-'.join(x.name.lower().split(' '))
for x in State.objects.all()]
-class LinkHeaderPagination(PageNumberPagination):
+class LinkHeaderMixin(object):
"""Provide pagination based on rfc5988.
This is the Link header, similar to how GitHub does it. See:
@@ -37,8 +38,6 @@ class LinkHeaderPagination(PageNumberPagination):
https://tools.ietf.org/html/rfc5988#section-5
https://developer.github.com/guides/traversing-with-pagination
"""
- page_size = settings.REST_RESULTS_PER_PAGE
- page_size_query_param = 'per_page'
def get_paginated_response(self, data):
next_url = self.get_next_link()
@@ -56,6 +55,22 @@ class LinkHeaderPagination(PageNumberPagination):
return Response(data, headers=headers)
+class LinkHeaderPageNumberPagination(LinkHeaderMixin, PageNumberPagination):
+ """A Link header-based variant of page-based paginator."""
+
+ page_size = settings.REST_RESULTS_PER_PAGE
+ page_size_query_param = 'per_page'
+
+
+LinkHeaderPagination = LinkHeaderPageNumberPagination
+
+
+class LinkHeaderCursorPagination(LinkHeaderMixin, CursorPagination):
+ """A Link header-based variant of cursor-based paginator."""
+
+ page_size = 100
+
+
class PatchworkPermission(permissions.BasePermission):
"""This permission works for Project and Patch model objects"""
def has_object_permission(self, request, view, obj):
diff --git a/patchwork/api/check.py b/patchwork/api/check.py
index dcdc5c5..393fcf2 100644
--- a/patchwork/api/check.py
+++ b/patchwork/api/check.py
@@ -88,10 +88,12 @@ class CheckSerializer(HyperlinkedModelSerializer):
class CheckMixin(object):
- queryset = Check.objects.prefetch_related('patch', 'user')
serializer_class = CheckSerializer
filter_class = CheckFilter
+ def get_queryset(self):
+ return Check.objects.prefetch_related('patch', 'user')
+
class CheckListCreate(CheckMixin, ListCreateAPIView):
"""List or create checks."""
diff --git a/patchwork/api/event.py b/patchwork/api/event.py
new file mode 100644
index 0000000..5265a19
--- /dev/null
+++ b/patchwork/api/event.py
@@ -0,0 +1,104 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2017 Stephen Finucane <stephen at that.guru>
+#
+# 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
+
+from django.core.urlresolvers import reverse
+from rest_framework.generics import ListAPIView
+from rest_framework.serializers import HyperlinkedModelSerializer
+from rest_framework.serializers import SerializerMethodField
+
+from patchwork.api.base import LinkHeaderCursorPagination
+from patchwork.api.filters import EventFilter
+from patchwork.api.patch import StateField
+from patchwork.models import Event
+
+
+class EventSerializer(HyperlinkedModelSerializer):
+
+ category = SerializerMethodField()
+ previous_state = StateField()
+ current_state = StateField()
+ created_check = SerializerMethodField()
+
+ _category_map = {
+ Event.CATEGORY_PATCH_CREATED: ['patch'],
+ Event.CATEGORY_PATCH_DEPENDENCIES_MET: ['patch', 'series'],
+ Event.CATEGORY_PATCH_STATE_CHANGED: ['patch', 'previous_state',
+ 'current_state'],
+ Event.CATEGORY_PATCH_DELEGATE_CHANGED: ['patch', 'previous_delegate',
+ 'current_delegate'],
+ Event.CATEGORY_PATCH_CHECK_CREATED: ['patch', 'created_check'],
+ Event.CATEGORY_COVER_CREATED: ['cover'],
+ Event.CATEGORY_SERIES_CREATED: ['series'],
+ }
+
+ def get_category(self, event):
+ return event.get_category_display()
+
+ def get_created_check(self, instance):
+ if not instance.patch or not instance.created_check:
+ return
+
+ return self.context.get('request').build_absolute_uri(
+ reverse('api-check-detail', kwargs={
+ 'patch_id': instance.patch.id,
+ 'check_id': instance.created_check.id}))
+
+ def to_representation(self, instance):
+ data = super(EventSerializer, self).to_representation(instance)
+
+ kept_fields = self._category_map[instance.category] + [
+ 'id', 'category', 'project', 'date']
+ for field in [x for x in data if x not in kept_fields]:
+ del data[field]
+
+ return data
+
+ class Meta:
+ model = Event
+ fields = ('id', 'category', 'project', 'date', 'patch', 'series',
+ 'cover', 'previous_state', 'current_state',
+ 'previous_delegate', 'current_delegate', 'created_check')
+ read_only_fields = fields
+ extra_kwargs = {
+ 'project': {'view_name': 'api-project-detail'},
+ 'patch': {'view_name': 'api-patch-detail'},
+ 'series': {'view_name': 'api-series-detail'},
+ 'cover': {'view_name': 'api-cover-detail'},
+ 'previous_delegate': {'view_name': 'api-user-detail'},
+ 'current_delegate': {'view_name': 'api-user-detail'},
+ 'created_check': {'view_name': 'api-check-detail'},
+ }
+
+
+class EventList(ListAPIView):
+ """List events."""
+
+ serializer_class = EventSerializer
+ pagination_class = LinkHeaderCursorPagination
+ filter_class = EventFilter
+ ordering = '-date'
+ ordering_fields = ()
+
+ def get_queryset(self):
+ return Event.objects.all()\
+ .select_related('project', 'patch', 'series', 'cover',
+ 'previous_state', 'current_state',
+ 'previous_delegate', 'current_delegate',
+ 'created_check')\
+ .order_by('-date')
diff --git a/patchwork/api/filters.py b/patchwork/api/filters.py
index f475ca8..4f91827 100644
--- a/patchwork/api/filters.py
+++ b/patchwork/api/filters.py
@@ -22,6 +22,7 @@ from django_filters import IsoDateTimeFilter
from patchwork.models import Check
from patchwork.models import CoverLetter
+from patchwork.models import Event
from patchwork.models import Patch
from patchwork.models import Series
@@ -60,3 +61,10 @@ class CheckFilter(TimestampMixin, FilterSet):
class Meta:
model = Check
fields = ('user', 'state', 'context')
+
+
+class EventFilter(FilterSet):
+
+ class Meta:
+ model = Event
+ fields = ('project', 'series', 'patch', 'cover')
diff --git a/patchwork/api/index.py b/patchwork/api/index.py
index 58aeb87..210c32e 100644
--- a/patchwork/api/index.py
+++ b/patchwork/api/index.py
@@ -33,4 +33,5 @@ class IndexView(APIView):
'patches': request.build_absolute_uri(reverse('api-patch-list')),
'covers': request.build_absolute_uri(reverse('api-cover-list')),
'series': request.build_absolute_uri(reverse('api-series-list')),
+ 'events': request.build_absolute_uri(reverse('api-event-list')),
})
diff --git a/patchwork/signals.py b/patchwork/signals.py
index 450cc57..2c1cc76 100644
--- a/patchwork/signals.py
+++ b/patchwork/signals.py
@@ -133,10 +133,11 @@ def create_patch_delegate_changed_event(sender, instance, **kwargs):
@receiver(post_save, sender=SeriesPatch)
def create_patch_dependencies_met_event(sender, instance, created, **kwargs):
- def create_event(patch):
+ def create_event(patch, series):
return Event.objects.create(
project=patch.project,
patch=patch,
+ series=series,
category=Event.CATEGORY_PATCH_DEPENDENCIES_MET)
if not created:
@@ -149,7 +150,7 @@ def create_patch_dependencies_met_event(sender, instance, created, **kwargs):
if predecessors.count() != instance.number - 1:
return
- create_event(instance.patch)
+ create_event(instance.patch, instance.series)
# if this satisfies dependencies for successor patch, raise events for
# those
@@ -159,7 +160,7 @@ def create_patch_dependencies_met_event(sender, instance, created, **kwargs):
if successor.number != count:
break
- create_event(successor.patch)
+ create_event(successor.patch, successor.series)
count += 1
@@ -173,7 +174,7 @@ def create_check_created_event(sender, instance, created, **kwargs):
project=check.patch.project,
patch=check.patch,
created_check=check,
- category=Event.CATEGORY_CHECK_CREATED)
+ category=Event.CATEGORY_PATCH_CHECK_CREATED)
if not created:
return
diff --git a/patchwork/urls.py b/patchwork/urls.py
index 68aefc2..fbba807 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -153,8 +153,9 @@ if settings.ENABLE_REST_API:
'djangorestframework must be installed to enable the REST API.')
from patchwork.api import check as api_check_views
- from patchwork.api import index as api_index_views
from patchwork.api import cover as api_cover_views
+ from patchwork.api import event as api_event_views
+ from patchwork.api import index as api_index_views
from patchwork.api import patch as api_patch_views
from patchwork.api import person as api_person_views
from patchwork.api import project as api_project_views
@@ -207,6 +208,9 @@ if settings.ENABLE_REST_API:
url(r'^projects/(?P<pk>[^/]+)/$',
api_project_views.ProjectDetail.as_view(),
name='api-project-detail'),
+ url(r'^events/$',
+ api_event_views.EventList.as_view(),
+ name='api-event-list'),
]
urlpatterns += [
--
2.9.3
More information about the Patchwork
mailing list