[PATCH 4/7] api: Add a basic REST API to access Series/Revisions and Patches

Damien Lespiau damien.lespiau at intel.com
Wed Oct 21 09:40:44 AEDT 2015


v2: Merge commits introducing basic objects
v3: Introduce the SeriesListMixin class
v4: Add the expand get parameter
v5: Introduce /api/1.0/ metadata entry point

Signed-off-by: Damien Lespiau <damien.lespiau at intel.com>
---
 docs/api.rst               | 363 +++++++++++++++++++++++++++++++++++++++++++++
 docs/conf.py               |   2 +-
 docs/index.rst             |   1 +
 docs/requirements-base.txt |   3 +
 docs/requirements-dev.txt  |   1 +
 patchwork/models.py        |   5 +-
 patchwork/serializers.py   | 133 +++++++++++++++++
 patchwork/settings/base.py |   7 +
 patchwork/urls.py          |  31 ++++
 patchwork/views/api.py     | 127 ++++++++++++++++
 10 files changed, 671 insertions(+), 2 deletions(-)
 create mode 100644 docs/api.rst
 create mode 100644 patchwork/serializers.py
 create mode 100644 patchwork/views/api.py

diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000..7979ac4
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,363 @@
+APIs
+===========
+
+REST API
+--------
+
+API metadata
+~~~~~~~~~~~~
+
+.. http:get:: /api/1.0/
+
+    Metadata about the API itself.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/ HTTP/1.1
+        Accept: application/json
+
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Vary: Accept
+        Content-Type: application/json
+
+        {
+            "revision": 0
+        }
+
+    :>json int revision: API revision. This can be used to ensure the server
+                         supports a feature introduced from a specific revision.
+
+
+Projects
+~~~~~~~~
+
+A project is merely one of the projects defined for this patchwork instance.
+
+.. http:get:: /api/1.0/projects/
+
+    List of all projects.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/projects/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Vary: Accept
+        Allow: GET, HEAD, OPTIONS
+
+        {
+            "count": 2,
+            "next": null,
+            "previous": null,
+            "results": [
+                {
+                    "id": 2,
+                    "name": "beignet",
+                    "linkname": "beignet",
+                    "listemail": "beignet at lists.freedesktop.org",
+                    "web_url": "http://www.freedesktop.org/wiki/Software/Beignet/",
+                    "scm_url": "git://anongit.freedesktop.org/git/beignet",
+                    "webscm_url": "http://cgit.freedesktop.org/beignet/"
+                },
+                {
+                    "id": 1,
+                    "name": "Cairo",
+                    "linkname": "cairo",
+                    "listemail": "cairo at cairographics.org",
+                    "web_url": "http://www.cairographics.org/",
+                    "scm_url": "git://anongit.freedesktop.org/git/cairo",
+                    "webscm_url": "http://cgit.freedesktop.org/cairo/"
+                }
+            ]
+        }
+
+.. http:get:: /api/1.0/projects/(string: linkname)/
+.. http:get:: /api/1.0/projects/(int: project_id)/
+
+    .. sourcecode:: http
+
+        GET /api/1.0/projects/intel-gfx/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Vary: Accept
+        Allow: GET, HEAD, OPTIONS
+
+        {
+            "id": 1,
+            "name": "intel-gfx",
+            "linkname": "intel-gfx",
+            "listemail": "intel-gfx at lists.freedesktop.org",
+            "web_url": "",
+            "scm_url": "",
+            "webscm_url": ""
+        }
+
+Series
+~~~~~~
+
+A series object represents a lists of patches sent to the mailing-list through
+``git-send-email``. It also includes all subsequent patches that are sent to
+address review comments, both single patch and full new series.
+
+A series has then ``n`` revisions, ``n`` going from ``1`` to ``version``.
+
+.. http:get:: /api/1.0/projects/(string: linkname)/series/
+.. http:get:: /api/1.0/projects/(int: project_id)/series/
+
+    List of all Series belonging to a specific project. The project can be
+    specified using either its ``linkname`` or ``id``.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/projects/intel-gfx/series/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Vary: Accept
+        Allow: GET, HEAD, OPTIONS
+
+        {
+            "count": 59,
+            "next": "http://patchwork.freedesktop.org/api/1.0/projects/intel-gfx/series/?page=2",
+            "previous": null,
+            "results": [
+                {
+                    "id": 3,
+                    "project": 1,
+                    "name": "drm/i915: Unwind partial VMA rebinding after failure in set-cache-level",
+                    "n_patches": 1,
+                    "submitter": 77,
+                    "submitted": "2015-10-09T11:51:38",
+                    "last_updated": "2015-10-09T11:51:59.013",
+                    "version": 1,
+                    "reviewer": null
+                },
+                {
+                    "id": 5,
+                    "project": 1,
+                    "name": "RFC drm/i915: Stop the machine whilst capturing the GPU crash dump",
+                    "n_patches": 1,
+                    "submitter": 77,
+                    "submitted": "2015-10-09T12:21:45",
+                    "last_updated": "2015-10-09T12:21:58.657",
+                    "version": 1,
+                    "reviewer": null,
+                }
+            ]
+        }
+
+.. http:get:: /api/1.0/series/
+
+    List of all Series known to patchwork.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/series/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Vary: Accept
+        Content-Type: application/json
+
+        {
+            "count": 344,
+            "next": "http://127.0.0.1:8000/api/1.0/series/?page=2",
+            "previous": null,
+            "results": [
+                {
+                    "id": 10,
+                    "project": 1,
+                    "name": "intel: New libdrm interface to create unbound wc user mappings for objects",
+                    "n_patches": 1,
+                    "submitter": 10,
+                    "submitted": "2015-01-02T11:06:40",
+                    "last_updated": "2015-10-09T07:55:18.608",
+                    "version": 1,
+                    "reviewer": null
+                },
+                {
+                    "id": 1,
+                    "project": 1,
+                    "name": "PMIC based Panel and Backlight Control",
+                    "n_patches": 4,
+                    "submitter": 1,
+                    "submitted": "2014-12-26T10:23:26",
+                    "last_updated": "2015-10-09T07:55:01.558",
+                    "version": 1,
+                    "reviewer": null,
+                },
+            ]
+        }
+
+.. http:get:: /api/1.0/series/(int: series_id)/
+
+    A series (`series_id`). A Series object contains metadata about the series.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/series/47/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Vary: Accept
+        Allow: GET, PUT, PATCH, HEAD, OPTIONS
+
+        {
+            "id": 47,
+            "name": "Series without cover letter",
+            "n_patches": 2,
+            "submitter": 21,
+            "submitted": "2015-01-13T09:32:24",
+            "last_updated": "2015-10-09T07:57:23.541",
+            "version": 1,
+            "reviewer": null
+        }
+
+.. http:get:: /api/1.0/series/(int: series_id)/revisions/
+
+    The list of revisions of the series `series_id`.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/series/47/revisions/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Vary: Accept
+        Allow: GET, HEAD, OPTIONS
+
+        [
+            {
+                "version": 1,
+                "cover_letter": null,
+                "patches": [
+                    120,
+                    121
+                ]
+            }
+        ]
+
+.. http:get:: /api/1.0/series/(int: series_id)/revisions/(int: version)/
+
+    The specific ``version`` of the series `series_id`.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/series/47/revisions/1/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Vary: Accept
+        Allow: GET, HEAD, OPTIONS
+
+        {
+            "version": 1,
+            "cover_letter": null,
+            "patches": [
+                120,
+                121
+            ]
+        }
+
+Patches
+~~~~~~~
+
+.. http:get:: /api/1.0/patches/
+
+    List of all patches.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/patches/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Vary: Accept
+        Allow: GET, HEAD, OPTIONS
+
+        {
+            "count": 1392,
+            "next": "http://127.0.0.1:8000/api/1.0/patches/?page=2",
+            "previous": null,
+            "results": [
+                {
+                    "id": 1,
+                    "project": 1,
+                    "name": "[RFC,1/4] drm/i915: Define a common data structure for Panel Info",
+                    "date": "2014-12-26T10:23:27",
+                    "submitter": 1,
+                    "state": 1,
+                    "content": "<diff content>"
+                },
+                {
+                    "id": 4,
+                    "project": 1,
+                    "name": "[RFC,2/4] drm/i915: Add a drm_panel over INTEL_SOC_PMIC",
+                    "date": "2014-12-26T10:23:28",
+                    "submitter": 1,
+                    "state": 1,
+                    "content": "<diff content>"
+                }
+            ]
+        }
+
+.. http:get:: /api/1.0/patches/(int: patch_id)/
+
+    A specific patch.
+
+    .. sourcecode:: http
+
+        GET /api/1.0/patches/120/ HTTP/1.1
+        Accept: application/json
+
+    .. sourcecode:: http
+
+            HTTP/1.1 200 OK
+            Content-Type: application/json
+            Vary: Accept
+            Allow: GET, HEAD, OPTIONS
+
+            {
+                "id": 120,
+                "name": "[1/2] drm/i915: Balance context pinning on reset cleanup",
+                "date": "2015-01-13T09:32:24",
+                "submitter": 21,
+                "state": 1,
+                "content": "<diff content>"
+            }
+
+API Revisions
+~~~~~~~~~~~~~
+
+**Revision 0**
+
+- Initial revision. Basic objects exposed: api root, projects, series,
+  revisions and patches.
diff --git a/docs/conf.py b/docs/conf.py
index d45985d..0bdc031 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -29,7 +29,7 @@ import shlex
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = []
+extensions = ['sphinxcontrib.httpdomain']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
diff --git a/docs/index.rst b/docs/index.rst
index 7322a9a..5351e30 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -14,5 +14,6 @@ Contents:
    intro
    installation
    manual
+   api
    development
 
diff --git a/docs/requirements-base.txt b/docs/requirements-base.txt
index 58b34ec..7314ab3 100644
--- a/docs/requirements-base.txt
+++ b/docs/requirements-base.txt
@@ -1,2 +1,5 @@
 MySQL-python==1.2.5
 python-dateutil==1.5
+djangorestframework>=2.4.8,<3.0.0
+drf-nested-routers
+enum34
diff --git a/docs/requirements-dev.txt b/docs/requirements-dev.txt
index 19a9b91..6021175 100644
--- a/docs/requirements-dev.txt
+++ b/docs/requirements-dev.txt
@@ -1,3 +1,4 @@
 -r requirements-base.txt
 selenium
 sphinx
+sphinxcontrib-httpdomain
diff --git a/patchwork/models.py b/patchwork/models.py
index 3ede65c..24fac6c 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -39,12 +39,15 @@ class Person(models.Model):
     user = models.ForeignKey(User, null = True, blank = True,
             on_delete = models.SET_NULL)
 
-    def __unicode__(self):
+    def display_name(self):
         if self.name:
             return self.name
         else:
             return self.email
 
+    def __unicode__(self):
+        return self.display_name()
+
     def link_to_user(self, user):
         self.name = user.profile.name()
         self.user = user
diff --git a/patchwork/serializers.py b/patchwork/serializers.py
new file mode 100644
index 0000000..418a140
--- /dev/null
+++ b/patchwork/serializers.py
@@ -0,0 +1,133 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2014 Intel Corporation
+#
+# 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.contrib.auth.models import User
+from patchwork.models import Project, Series, SeriesRevision, Patch, Person, \
+                             State
+from rest_framework import serializers
+from enum import Enum
+
+class RelatedMode(Enum):
+    """Select how to show related fields in the JSON responses."""
+    primary_key = 1
+    expand = 2
+
+class PatchworkModelSerializerOptions(serializers.ModelSerializerOptions):
+    """Meta class options for PatchworkModelSerializer"""
+    def __init__(self, meta):
+        super(PatchworkModelSerializerOptions, self).__init__(meta)
+        self.expand_serializers = getattr(meta, 'expand_serializers', {})
+
+class PatchworkModelSerializer(serializers.ModelSerializer):
+    """A model serializer with configurable related fields.
+
+       PatchworkModelSerializer can either show related fields as a integer
+       or expand them to include the related full JSON object.
+       This behaviour is selectable through the 'related' GET parameter. Adding
+       'related=expand' to the GET request will expand related fields.
+    """
+
+    _options_class = PatchworkModelSerializerOptions
+
+    def __init__(self, *args, **kwargs):
+        super(PatchworkModelSerializer, self).__init__(*args, **kwargs)
+
+        self._pw_related = RelatedMode.primary_key
+        related = self.context['request'].QUERY_PARAMS.get('related')
+        if not related:
+            return
+
+        try:
+            self._pw_related = RelatedMode[related]
+        except KeyError:
+            pass
+
+    def _pw_get_nested_field(self, model_field, related_model, to_many):
+        class NestedModelSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = related_model
+
+        if model_field.name in self.opts.expand_serializers:
+            serializer_class = self.opts.expand_serializers[model_field.name]
+            return serializer_class(context=self.context, many=to_many)
+        return NestedModelSerializer(many=to_many)
+
+    def get_related_field(self, model_field, related_model, to_many):
+        if self._pw_related == RelatedMode.expand:
+            return self._pw_get_nested_field(model_field, related_model, to_many)
+        else:
+            return super(PatchworkModelSerializer, self). \
+                    get_related_field(model_field, related_model, to_many)
+
+class UserSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = User
+        fields = ('id', 'username', 'first_name', 'last_name', )
+
+class PersonSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='display_name', read_only=True)
+    class Meta:
+        model = Person
+        fields = ('id', 'name', )
+
+class ProjectSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Project
+        fields = ('id', 'name', 'linkname', 'listemail', 'web_url', 'scm_url',
+                  'webscm_url')
+
+class StateSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = State
+        fields = ('id', 'name')
+
+class SeriesSerializer(PatchworkModelSerializer):
+    class Meta:
+        model = Series
+        fields = ('id', 'project', 'name', 'n_patches', 'submitter',
+                  'submitted', 'last_updated', 'version', 'reviewer')
+        read_only_fields = ('project', 'n_patches', 'submitter', 'submitted',
+                            'last_updated', 'version')
+        expand_serializers = {
+            'project': ProjectSerializer,
+            'submitter': PersonSerializer,
+            'reviewer': UserSerializer,
+        }
+
+class PatchSerializer(PatchworkModelSerializer):
+    class Meta:
+        model = Patch
+        fields = ('id', 'project', 'name', 'date', 'submitter', 'state',
+                  'content')
+        read_only_fields = ('id', 'project', 'name', 'date', 'submitter',
+                            'content')
+        expand_serializers = {
+            'project': ProjectSerializer,
+            'submitter': PersonSerializer,
+            'state': StateSerializer,
+        }
+
+class RevisionSerializer(PatchworkModelSerializer):
+    class Meta:
+        model = SeriesRevision
+        fields = ('version', 'cover_letter', 'patches')
+        read_only_fields = ('version', 'cover_letter')
+        expand_serializers = {
+            'patches': PatchSerializer,
+        }
diff --git a/patchwork/settings/base.py b/patchwork/settings/base.py
index ab03814..6edf7c6 100644
--- a/patchwork/settings/base.py
+++ b/patchwork/settings/base.py
@@ -24,6 +24,7 @@ INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.staticfiles',
     'patchwork',
+    'rest_framework',
 ]
 
 # HTTP
@@ -97,6 +98,12 @@ STATICFILES_DIRS = [
     os.path.join(ROOT_DIR, 'htdocs'),
 ]
 
+#
+# REST framework
+
+REST_FRAMEWORK = {
+}
+
 
 #
 # Patchwork settings
diff --git a/patchwork/urls.py b/patchwork/urls.py
index bc792d0..3ba734e 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -21,12 +21,43 @@ from django.conf.urls import patterns, url, include
 from django.conf import settings
 from django.contrib import admin
 from django.contrib.auth import views as auth_views
+from rest_framework_nested import routers
+import patchwork.views.api as api
+
+# API
+
+# /projects/$project/
+project_router = routers.SimpleRouter()
+project_router.register('projects', api.ProjectViewSet)
+# /projects/$project/series/
+series_list_router = routers.NestedSimpleRouter(project_router, 'projects',
+                                                lookup='project')
+series_list_router.register(r'series', api.SeriesListViewSet)
+# /series/$id/
+series_router = routers.SimpleRouter()
+series_router.register(r'series', api.SeriesViewSet)
+# /series/$id/revisions/$rev
+revisions_router = routers.NestedSimpleRouter(series_router, 'series',
+                                             lookup='series')
+revisions_router.register(r'revisions', api.RevisionViewSet)
+# /patches/$id/
+patches_router = routers.SimpleRouter()
+patches_router.register(r'patches', api.PatchViewSet)
 
 admin.autodiscover()
 
 urlpatterns = patterns('',
     url(r'^admin/', include(admin.site.urls)),
 
+    # API
+    (r'^api/1.0/$', api.API.as_view()),
+    (r'^api/1.0/', include(project_router.urls)),
+    (r'^api/1.0/', include(series_list_router.urls)),
+    (r'^api/1.0/', include(series_router.urls)),
+    (r'^api/1.0/', include(revisions_router.urls)),
+    (r'^api/1.0/', include(patches_router.urls)),
+
+    # project view:
     (r'^$', 'patchwork.views.projects'),
     (r'^project/(?P<project_id>[^/]+)/list/$', 'patchwork.views.patch.list'),
     (r'^project/(?P<project_id>[^/]+)/$', 'patchwork.views.project.project'),
diff --git a/patchwork/views/api.py b/patchwork/views/api.py
new file mode 100644
index 0000000..01539b6
--- /dev/null
+++ b/patchwork/views/api.py
@@ -0,0 +1,127 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2014 Intel Corporation
+#
+# 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 patchwork.models import Project, Series, SeriesRevision, Patch
+from rest_framework import views, viewsets, mixins, generics, filters, permissions
+from rest_framework.decorators import api_view, renderer_classes, \
+                                      permission_classes
+from rest_framework.renderers import JSONRenderer
+from rest_framework.response import Response
+from rest_framework.generics import get_object_or_404
+from patchwork.serializers import ProjectSerializer, SeriesSerializer, \
+                                  RevisionSerializer, PatchSerializer
+
+
+API_REVISION = 0
+
+class MaintainerPermission(permissions.BasePermission):
+    def has_object_permission(self, request, view, obj):
+        # read only for everyone
+        if request.method in permissions.SAFE_METHODS:
+            return True
+
+        # editable for maintainers
+        user = request.user
+        if not user.is_authenticated():
+            return False
+        return obj.project.is_editable(user)
+
+class API(views.APIView):
+    permission_classes = (permissions.AllowAny,)
+
+    def get(self, request, format=None):
+        return Response({ 'revision': API_REVISION })
+
+class ListMixin(object):
+    paginate_by = 20
+    paginate_by_param = 'perpage'
+    max_paginate_by = 100
+
+class SeriesListMixin(ListMixin):
+    queryset = Series.objects.all()
+    serializer_class = SeriesSerializer
+    filter_backends = (filters.OrderingFilter, )
+    ordering_fields = ('name', 'n_patches', 'submitter__name', 'reviewer__name',
+                        'submitted', 'last_updated')
+
+def is_integer(s):
+    try:
+        int(s)
+        return True
+    except ValueError:
+        return False
+
+class ProjectViewSet(mixins.ListModelMixin, ListMixin, viewsets.GenericViewSet):
+    permission_classes = (MaintainerPermission, )
+    queryset = Project.objects.all()
+    serializer_class = ProjectSerializer
+
+    def retrieve(self, request, pk=None):
+        if is_integer(pk):
+            queryset = get_object_or_404(Project, pk=pk)
+        else:
+            queryset = get_object_or_404(Project, linkname=pk)
+        serializer = ProjectSerializer(queryset)
+        return Response(serializer.data)
+
+class SeriesListViewSet(mixins.ListModelMixin,
+                        SeriesListMixin,
+                        viewsets.GenericViewSet):
+    permission_classes = (MaintainerPermission, )
+
+    def get_queryset(self):
+
+        pk = self.kwargs['project_pk']
+        if is_integer(pk):
+            queryset = self.queryset.filter(project__pk=pk)
+        else:
+            queryset = self.queryset.filter(project__linkname=pk)
+        return queryset
+
+class SeriesViewSet(mixins.ListModelMixin,
+                    mixins.RetrieveModelMixin,
+                    SeriesListMixin,
+                    viewsets.GenericViewSet):
+    permission_classes = (MaintainerPermission, )
+    queryset = Series.objects.all()
+
+class RevisionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
+    permission_classes = (MaintainerPermission, )
+    queryset = SeriesRevision.objects.all()
+    serializer_class = RevisionSerializer
+
+    def get_queryset(self):
+
+        series_pk = self.kwargs['series_pk']
+        return self.queryset.filter(series=series_pk)
+
+    def retrieve(self, request, series_pk=None, pk=None):
+        rev = get_object_or_404(SeriesRevision, series=series_pk, version=pk)
+        print(self.get_serializer_context())
+        serializer = RevisionSerializer(rev,
+                                        context=self.get_serializer_context())
+        return Response(serializer.data)
+
+class PatchViewSet(mixins.ListModelMixin,
+                   mixins.RetrieveModelMixin,
+                   ListMixin,
+                   viewsets.GenericViewSet):
+    permission_classes = (MaintainerPermission, )
+    queryset = Patch.objects.all()
+    serializer_class = PatchSerializer
-- 
2.4.3



More information about the Patchwork mailing list