Commit 64fda6d3 authored by Damien Lespiau's avatar Damien Lespiau
Browse files

api: Add a basic REST API to access Series/Revisions and Patches



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: default avatarDamien Lespiau <damien.lespiau@intel.com>
parent 69c4516a
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@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@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@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.
......@@ -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']
......
......@@ -14,5 +14,6 @@ Contents:
intro
installation
manual
api
development
MySQL-python==1.2.5
python-dateutil==1.5
djangorestframework>=2.4.8,<3.0.0
drf-nested-routers
enum34
-r requirements-base.txt
selenium
sphinx
sphinxcontrib-httpdomain
......@@ -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
......
# 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,
}
......@@ -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
......
......@@ -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'),
......
# 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):