[PATCH v3 02/16] REST: Create 'api' directory

Stephen Finucane stephen at that.guru
Sat Nov 26 05:18:21 AEDT 2016


Move all REST API-related code into an 'api' directory. This allows us
to break the existing files into endpoint-based files and will allow us
to split the API into a different Django app in the future. This
involves simply shuffling code around for now, so there no functional
changes introduced.

Signed-off-by: Stephen Finucane <stephen at that.guru>
---
v3:
- Don't use '__init__' for declaring code (Daniel Axtens)
v2:
- Rebase onto master
---
 patchwork/api/__init__.py     |   0
 patchwork/api/base.py         |  91 ++++++++++++++++++++++
 patchwork/api/check.py        |  98 +++++++++++++++++++++++
 patchwork/api/patch.py        |  84 ++++++++++++++++++++
 patchwork/api/person.py       |  38 +++++++++
 patchwork/api/project.py      |  60 +++++++++++++++
 patchwork/api/user.py         |  37 +++++++++
 patchwork/rest_serializers.py | 147 -----------------------------------
 patchwork/urls.py             |  20 ++++-
 patchwork/views/rest_api.py   | 175 ------------------------------------------
 10 files changed, 427 insertions(+), 323 deletions(-)
 create mode 100644 patchwork/api/__init__.py
 create mode 100644 patchwork/api/base.py
 create mode 100644 patchwork/api/check.py
 create mode 100644 patchwork/api/patch.py
 create mode 100644 patchwork/api/person.py
 create mode 100644 patchwork/api/project.py
 create mode 100644 patchwork/api/user.py
 delete mode 100644 patchwork/rest_serializers.py
 delete mode 100644 patchwork/views/rest_api.py

diff --git a/patchwork/api/__init__.py b/patchwork/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/patchwork/api/base.py b/patchwork/api/base.py
new file mode 100644
index 0000000..7333a7f
--- /dev/null
+++ b/patchwork/api/base.py
@@ -0,0 +1,91 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Linaro 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.conf import settings
+from rest_framework import permissions
+from rest_framework.pagination import PageNumberPagination
+from rest_framework.response import Response
+from rest_framework.serializers import HyperlinkedModelSerializer
+from rest_framework.serializers import HyperlinkedRelatedField
+from rest_framework.viewsets import ModelViewSet
+
+
+class URLSerializer(HyperlinkedModelSerializer):
+    """Just like parent but puts _url for fields"""
+
+    def to_representation(self, instance):
+        data = super(URLSerializer, self).to_representation(instance)
+        for name, field in self.fields.items():
+            if isinstance(field, HyperlinkedRelatedField) and name != 'url':
+                data[name + '_url'] = data.pop(name)
+        return data
+
+
+class LinkHeaderPagination(PageNumberPagination):
+    """Provide pagination based on rfc5988.
+
+    This is the Link header, similar to how GitHub does it. See:
+
+       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()
+        previous_url = self.get_previous_link()
+
+        link = ''
+        if next_url is not None and previous_url is not None:
+            link = '<{next_url}>; rel="next", <{previous_url}>; rel="prev"'
+        elif next_url is not None:
+            link = '<{next_url}>; rel="next"'
+        elif previous_url is not None:
+            link = '<{previous_url}>; rel="prev"'
+        link = link.format(next_url=next_url, previous_url=previous_url)
+        headers = {'Link': link} if link else {}
+        return Response(data, headers=headers)
+
+
+class PatchworkPermission(permissions.BasePermission):
+    """This permission works for Project and Patch model objects"""
+    def has_permission(self, request, view):
+        if request.method in ('POST', 'DELETE'):
+            return False
+        return super(PatchworkPermission, self).has_permission(request, view)
+
+    def has_object_permission(self, request, view, obj):
+        # read only for everyone
+        if request.method in permissions.SAFE_METHODS:
+            return True
+        return obj.is_editable(request.user)
+
+
+class AuthenticatedReadOnly(permissions.BasePermission):
+    def has_permission(self, request, view):
+        authenticated = request.user.is_authenticated()
+        return authenticated and request.method in permissions.SAFE_METHODS
+
+
+class PatchworkViewSet(ModelViewSet):
+    pagination_class = LinkHeaderPagination
+
+    def get_queryset(self):
+        return self.serializer_class.Meta.model.objects.all()
diff --git a/patchwork/api/check.py b/patchwork/api/check.py
new file mode 100644
index 0000000..31ade07
--- /dev/null
+++ b/patchwork/api/check.py
@@ -0,0 +1,98 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Linaro 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.core.urlresolvers import reverse
+from rest_framework.exceptions import PermissionDenied
+from rest_framework.relations import HyperlinkedRelatedField
+from rest_framework.response import Response
+from rest_framework.serializers import CurrentUserDefault
+from rest_framework.serializers import HiddenField
+from rest_framework.serializers import ModelSerializer
+
+from patchwork.api.base import PatchworkViewSet
+from patchwork.models import Check
+from patchwork.models import Patch
+
+
+class CurrentPatchDefault(object):
+    def set_context(self, serializer_field):
+        self.patch = serializer_field.context['request'].patch
+
+    def __call__(self):
+        return self.patch
+
+
+class CheckSerializer(ModelSerializer):
+    user = HyperlinkedRelatedField(
+        'user-detail', read_only=True, default=CurrentUserDefault())
+    patch = HiddenField(default=CurrentPatchDefault())
+
+    def run_validation(self, data):
+        for val, label in Check.STATE_CHOICES:
+            if label == data['state']:
+                data['state'] = val
+                break
+        return super(CheckSerializer, self).run_validation(data)
+
+    def to_representation(self, instance):
+        data = super(CheckSerializer, self).to_representation(instance)
+        data['state'] = instance.get_state_display()
+        # drf-nested doesn't handle HyperlinkedModelSerializers properly,
+        # so we have to put the url in by hand here.
+        url = self.context['request'].build_absolute_uri(reverse(
+            'api_1.0:patch-detail', args=[instance.patch.id]))
+        data['url'] = url + 'checks/%s/' % instance.id
+        data['users_url'] = data.pop('user')
+        return data
+
+    class Meta:
+        model = Check
+        fields = ('patch', 'user', 'date', 'state', 'target_url',
+                  'description', 'context',)
+        read_only_fields = ('date',)
+
+
+class CheckViewSet(PatchworkViewSet):
+    serializer_class = CheckSerializer
+
+    def not_allowed(self, request, **kwargs):
+        raise PermissionDenied()
+
+    update = not_allowed
+    partial_update = not_allowed
+    destroy = not_allowed
+
+    def create(self, request, patch_pk):
+        p = Patch.objects.get(id=patch_pk)
+        if not p.is_editable(request.user):
+            raise PermissionDenied()
+        request.patch = p
+        return super(CheckViewSet, self).create(request)
+
+    def list(self, request, patch_pk):
+        queryset = self.filter_queryset(self.get_queryset())
+        queryset = queryset.filter(patch=patch_pk)
+
+        page = self.paginate_queryset(queryset)
+        if page is not None:
+            serializer = self.get_serializer(page, many=True)
+            return self.get_paginated_response(serializer.data)
+
+        serializer = self.get_serializer(queryset, many=True)
+        return Response(serializer.data)
diff --git a/patchwork/api/patch.py b/patchwork/api/patch.py
new file mode 100644
index 0000000..c36a11b
--- /dev/null
+++ b/patchwork/api/patch.py
@@ -0,0 +1,84 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Linaro 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
+
+import email.parser
+
+from rest_framework.serializers import ListSerializer
+from rest_framework.serializers import SerializerMethodField
+
+from patchwork.api.base import PatchworkPermission
+from patchwork.api.base import PatchworkViewSet
+from patchwork.api.base import URLSerializer
+from patchwork.models import Patch
+
+
+class PatchListSerializer(ListSerializer):
+    """Semi hack to make the list of patches more efficient"""
+    def to_representation(self, data):
+        del self.child.fields['content']
+        del self.child.fields['headers']
+        del self.child.fields['diff']
+        return super(PatchListSerializer, self).to_representation(data)
+
+
+class PatchSerializer(URLSerializer):
+    mbox_url = SerializerMethodField()
+    state = SerializerMethodField()
+
+    class Meta:
+        model = Patch
+        list_serializer_class = PatchListSerializer
+        read_only_fields = ('project', 'name', 'date', 'submitter', 'diff',
+                            'content', 'hash', 'msgid')
+        # there's no need to expose an entire "tags" endpoint, so we custom
+        # render this field
+        exclude = ('tags',)
+
+    def get_state(self, obj):
+        return obj.state.name
+
+    def get_mbox_url(self, patch):
+        request = self.context.get('request', None)
+        return request.build_absolute_uri(patch.get_mbox_url())
+
+    def to_representation(self, instance):
+        data = super(PatchSerializer, self).to_representation(instance)
+        data['checks_url'] = data['url'] + 'checks/'
+        data['check'] = instance.combined_check_state
+        headers = data.get('headers')
+        if headers is not None:
+            data['headers'] = email.parser.Parser().parsestr(headers, True)
+        data['tags'] = [{'name': x.tag.name, 'count': x.count}
+                        for x in instance.patchtag_set.all()]
+        return data
+
+
+class PatchViewSet(PatchworkViewSet):
+    permission_classes = (PatchworkPermission,)
+    serializer_class = PatchSerializer
+
+    def get_queryset(self):
+        qs = super(PatchViewSet, self).get_queryset(
+        ).prefetch_related(
+            'check_set', 'patchtag_set'
+        ).select_related('state', 'submitter', 'delegate')
+        if 'pk' not in self.kwargs:
+            # we are doing a listing, we don't need these fields
+            qs = qs.defer('content', 'diff', 'headers')
+        return qs
diff --git a/patchwork/api/person.py b/patchwork/api/person.py
new file mode 100644
index 0000000..b1168f7
--- /dev/null
+++ b/patchwork/api/person.py
@@ -0,0 +1,38 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Linaro 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.api.base import AuthenticatedReadOnly
+from patchwork.api.base import PatchworkViewSet
+from patchwork.api.base import URLSerializer
+from patchwork.models import Person
+
+
+class PersonSerializer(URLSerializer):
+    class Meta:
+        model = Person
+        fields = ('email', 'name', 'user')
+
+
+class PeopleViewSet(PatchworkViewSet):
+    permission_classes = (AuthenticatedReadOnly,)
+    serializer_class = PersonSerializer
+
+    def get_queryset(self):
+        qs = super(PeopleViewSet, self).get_queryset()
+        return qs.prefetch_related('user')
diff --git a/patchwork/api/project.py b/patchwork/api/project.py
new file mode 100644
index 0000000..7def2ed
--- /dev/null
+++ b/patchwork/api/project.py
@@ -0,0 +1,60 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Linaro 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 rest_framework.serializers import HyperlinkedModelSerializer
+
+from patchwork.api.base import PatchworkPermission
+from patchwork.api.base import PatchworkViewSet
+from patchwork.models import Project
+
+
+class ProjectSerializer(HyperlinkedModelSerializer):
+    class Meta:
+        model = Project
+        exclude = ('send_notifications', 'use_tags')
+
+    def to_representation(self, instance):
+        data = super(ProjectSerializer, self).to_representation(instance)
+        data['link_name'] = data.pop('linkname')
+        data['list_email'] = data.pop('listemail')
+        data['list_id'] = data.pop('listid')
+        return data
+
+
+class ProjectViewSet(PatchworkViewSet):
+    permission_classes = (PatchworkPermission,)
+    serializer_class = ProjectSerializer
+
+    def _handle_linkname(self, pk):
+        '''Make it easy for users to list by project-id or linkname'''
+        qs = self.get_queryset()
+        try:
+            qs.get(id=pk)
+        except (self.serializer_class.Meta.model.DoesNotExist, ValueError):
+            # probably a non-numeric value which means we are going by linkname
+            self.kwargs = {'linkname': pk}  # try and lookup by linkname
+            self.lookup_field = 'linkname'
+
+    def retrieve(self, request, pk=None):
+        self._handle_linkname(pk)
+        return super(ProjectViewSet, self).retrieve(request, pk)
+
+    def partial_update(self, request, pk=None):
+        self._handle_linkname(pk)
+        return super(ProjectViewSet, self).partial_update(request, pk)
diff --git a/patchwork/api/user.py b/patchwork/api/user.py
new file mode 100644
index 0000000..b25054f
--- /dev/null
+++ b/patchwork/api/user.py
@@ -0,0 +1,37 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Linaro 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 rest_framework.serializers import HyperlinkedModelSerializer
+
+from patchwork.api.base import AuthenticatedReadOnly
+from patchwork.api.base import PatchworkViewSet
+
+
+class UserSerializer(HyperlinkedModelSerializer):
+    class Meta:
+        model = User
+        exclude = ('date_joined', 'groups', 'is_active', 'is_staff',
+                   'is_superuser', 'last_login', 'password',
+                   'user_permissions')
+
+
+class UserViewSet(PatchworkViewSet):
+    permission_classes = (AuthenticatedReadOnly,)
+    serializer_class = UserSerializer
diff --git a/patchwork/rest_serializers.py b/patchwork/rest_serializers.py
deleted file mode 100644
index 7bbad8d..0000000
--- a/patchwork/rest_serializers.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2016 Linaro 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
-
-import email.parser
-
-from django.contrib.auth.models import User
-from django.core.urlresolvers import reverse
-
-from rest_framework.relations import HyperlinkedRelatedField
-from rest_framework.serializers import (
-    CurrentUserDefault, HiddenField, HyperlinkedModelSerializer,
-    ListSerializer, ModelSerializer, SerializerMethodField)
-
-from patchwork.models import Check, Patch, Person, Project
-
-
-class URLSerializer(HyperlinkedModelSerializer):
-    """Just like parent but puts _url for fields"""
-
-    def to_representation(self, instance):
-        data = super(URLSerializer, self).to_representation(instance)
-        for name, field in self.fields.items():
-            if isinstance(field, HyperlinkedRelatedField) and name != 'url':
-                data[name + '_url'] = data.pop(name)
-        return data
-
-
-class PersonSerializer(URLSerializer):
-    class Meta:
-        model = Person
-        fields = ('email', 'name', 'user',)
-
-
-class UserSerializer(HyperlinkedModelSerializer):
-    class Meta:
-        model = User
-        exclude = ('date_joined', 'groups', 'is_active', 'is_staff',
-                   'is_superuser', 'last_login', 'password',
-                   'user_permissions')
-
-
-class ProjectSerializer(HyperlinkedModelSerializer):
-    class Meta:
-        model = Project
-        exclude = ('send_notifications', 'use_tags')
-
-    def to_representation(self, instance):
-        data = super(ProjectSerializer, self).to_representation(instance)
-        data['link_name'] = data.pop('linkname')
-        data['list_email'] = data.pop('listemail')
-        data['list_id'] = data.pop('listid')
-        return data
-
-
-class PatchListSerializer(ListSerializer):
-    """Semi hack to make the list of patches more efficient"""
-    def to_representation(self, data):
-        del self.child.fields['content']
-        del self.child.fields['headers']
-        del self.child.fields['diff']
-        return super(PatchListSerializer, self).to_representation(data)
-
-
-class PatchSerializer(URLSerializer):
-    class Meta:
-        model = Patch
-        list_serializer_class = PatchListSerializer
-        read_only_fields = ('project', 'name', 'date', 'submitter', 'diff',
-                            'content', 'hash', 'msgid')
-        # there's no need to expose an entire "tags" endpoint, so we custom
-        # render this field
-        exclude = ('tags',)
-    check_names = dict(Check.STATE_CHOICES)
-    mbox_url = SerializerMethodField()
-    state = SerializerMethodField()
-
-    def get_state(self, obj):
-        return obj.state.name
-
-    def get_mbox_url(self, patch):
-        request = self.context.get('request', None)
-        return request.build_absolute_uri(patch.get_mbox_url())
-
-    def to_representation(self, instance):
-        data = super(PatchSerializer, self).to_representation(instance)
-        data['checks_url'] = data['url'] + 'checks/'
-        data['check'] = instance.combined_check_state
-        headers = data.get('headers')
-        if headers is not None:
-            data['headers'] = email.parser.Parser().parsestr(headers, True)
-        data['tags'] = [{'name': x.tag.name, 'count': x.count}
-                        for x in instance.patchtag_set.all()]
-        return data
-
-
-class CurrentPatchDefault(object):
-    def set_context(self, serializer_field):
-        self.patch = serializer_field.context['request'].patch
-
-    def __call__(self):
-        return self.patch
-
-
-class ChecksSerializer(ModelSerializer):
-    user = HyperlinkedRelatedField(
-        'user-detail', read_only=True, default=CurrentUserDefault())
-    patch = HiddenField(default=CurrentPatchDefault())
-
-    def run_validation(self, data):
-        for val, label in Check.STATE_CHOICES:
-            if label == data['state']:
-                data['state'] = val
-                break
-        return super(ChecksSerializer, self).run_validation(data)
-
-    def to_representation(self, instance):
-        data = super(ChecksSerializer, self).to_representation(instance)
-        data['state'] = instance.get_state_display()
-        # drf-nested doesn't handle HyperlinkedModelSerializers properly,
-        # so we have to put the url in by hand here.
-        url = self.context['request'].build_absolute_uri(reverse(
-            'api_1.0:patch-detail', args=[instance.patch.id]))
-        data['url'] = url + 'checks/%s/' % instance.id
-        data['users_url'] = data.pop('user')
-        return data
-
-    class Meta:
-        model = Check
-        fields = ('patch', 'user', 'date', 'state', 'target_url',
-                  'description', 'context',)
-        read_only_fields = ('date',)
diff --git a/patchwork/urls.py b/patchwork/urls.py
index 98b4dbe..6671837 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -151,7 +151,25 @@ if settings.ENABLE_REST_API:
     if 'rest_framework' not in settings.INSTALLED_APPS:
         raise RuntimeError(
             'djangorestframework must be installed to enable the REST API.')
-    from patchwork.views.rest_api import router, patches_router
+
+    from rest_framework.routers import DefaultRouter
+    from rest_framework_nested.routers import NestedSimpleRouter
+
+    from patchwork.api.check import CheckViewSet
+    from patchwork.api.patch import PatchViewSet
+    from patchwork.api.person import PeopleViewSet
+    from patchwork.api.project import ProjectViewSet
+    from patchwork.api.user import UserViewSet
+
+    router = DefaultRouter()
+    router.register('patches', PatchViewSet, 'patch')
+    router.register('people', PeopleViewSet, 'person')
+    router.register('projects', ProjectViewSet, 'project')
+    router.register('users', UserViewSet, 'user')
+
+    patches_router = NestedSimpleRouter(router, r'patches', lookup='patch')
+    patches_router.register(r'checks', CheckViewSet, base_name='patch-checks')
+
     urlpatterns += [
         url(r'^api/1.0/', include(router.urls, namespace='api_1.0')),
         url(r'^api/1.0/', include(patches_router.urls, namespace='api_1.0')),
diff --git a/patchwork/views/rest_api.py b/patchwork/views/rest_api.py
deleted file mode 100644
index 9c58669..0000000
--- a/patchwork/views/rest_api.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2016 Linaro 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.conf import settings
-from patchwork.models import Patch
-from patchwork.rest_serializers import (
-    ChecksSerializer, PatchSerializer, PersonSerializer, ProjectSerializer,
-    UserSerializer)
-
-from rest_framework import permissions
-from rest_framework.exceptions import PermissionDenied
-from rest_framework.pagination import PageNumberPagination
-from rest_framework.response import Response
-from rest_framework.routers import DefaultRouter
-from rest_framework.viewsets import ModelViewSet
-from rest_framework_nested.routers import NestedSimpleRouter
-
-
-class LinkHeaderPagination(PageNumberPagination):
-    """Provide pagination based on rfc5988 (how github does it)
-       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()
-        previous_url = self.get_previous_link()
-
-        link = ''
-        if next_url is not None and previous_url is not None:
-            link = '<{next_url}>; rel="next", <{previous_url}>; rel="prev"'
-        elif next_url is not None:
-            link = '<{next_url}>; rel="next"'
-        elif previous_url is not None:
-            link = '<{previous_url}>; rel="prev"'
-        link = link.format(next_url=next_url, previous_url=previous_url)
-        headers = {'Link': link} if link else {}
-        return Response(data, headers=headers)
-
-
-class PatchworkPermission(permissions.BasePermission):
-    """This permission works for Project and Patch model objects"""
-    def has_permission(self, request, view):
-        if request.method in ('POST', 'DELETE'):
-            return False
-        return super(PatchworkPermission, self).has_permission(request, view)
-
-    def has_object_permission(self, request, view, obj):
-        # read only for everyone
-        if request.method in permissions.SAFE_METHODS:
-            return True
-        return obj.is_editable(request.user)
-
-
-class AuthenticatedReadOnly(permissions.BasePermission):
-    def has_permission(self, request, view):
-        authenticated = request.user.is_authenticated()
-        return authenticated and request.method in permissions.SAFE_METHODS
-
-
-class PatchworkViewSet(ModelViewSet):
-    pagination_class = LinkHeaderPagination
-
-    def get_queryset(self):
-        return self.serializer_class.Meta.model.objects.all()
-
-
-class UserViewSet(PatchworkViewSet):
-    permission_classes = (AuthenticatedReadOnly, )
-    serializer_class = UserSerializer
-
-
-class PeopleViewSet(PatchworkViewSet):
-    permission_classes = (AuthenticatedReadOnly, )
-    serializer_class = PersonSerializer
-
-    def get_queryset(self):
-        qs = super(PeopleViewSet, self).get_queryset()
-        return qs.prefetch_related('user')
-
-
-class ProjectViewSet(PatchworkViewSet):
-    permission_classes = (PatchworkPermission, )
-    serializer_class = ProjectSerializer
-
-    def _handle_linkname(self, pk):
-        '''Make it easy for users to list by project-id or linkname'''
-        qs = self.get_queryset()
-        try:
-            qs.get(id=pk)
-        except (self.serializer_class.Meta.model.DoesNotExist, ValueError):
-            # probably a non-numeric value which means we are going by linkname
-            self.kwargs = {'linkname': pk}  # try and lookup by linkname
-            self.lookup_field = 'linkname'
-
-    def retrieve(self, request, pk=None):
-        self._handle_linkname(pk)
-        return super(ProjectViewSet, self).retrieve(request, pk)
-
-    def partial_update(self, request, pk=None):
-        self._handle_linkname(pk)
-        return super(ProjectViewSet, self).partial_update(request, pk)
-
-
-class PatchViewSet(PatchworkViewSet):
-    permission_classes = (PatchworkPermission,)
-    serializer_class = PatchSerializer
-
-    def get_queryset(self):
-        qs = super(PatchViewSet, self).get_queryset(
-        ).prefetch_related(
-            'check_set', 'patchtag_set'
-        ).select_related('state', 'submitter', 'delegate')
-        if 'pk' not in self.kwargs:
-            # we are doing a listing, we don't need these fields
-            qs = qs.defer('content', 'diff', 'headers')
-        return qs
-
-
-class CheckViewSet(PatchworkViewSet):
-    serializer_class = ChecksSerializer
-
-    def not_allowed(self, request, **kwargs):
-        raise PermissionDenied()
-
-    update = not_allowed
-    partial_update = not_allowed
-    destroy = not_allowed
-
-    def create(self, request, patch_pk):
-        p = Patch.objects.get(id=patch_pk)
-        if not p.is_editable(request.user):
-            raise PermissionDenied()
-        request.patch = p
-        return super(CheckViewSet, self).create(request)
-
-    def list(self, request, patch_pk):
-        queryset = self.filter_queryset(self.get_queryset())
-        queryset = queryset.filter(patch=patch_pk)
-
-        page = self.paginate_queryset(queryset)
-        if page is not None:
-            serializer = self.get_serializer(page, many=True)
-            return self.get_paginated_response(serializer.data)
-
-        serializer = self.get_serializer(queryset, many=True)
-        return Response(serializer.data)
-
-
-router = DefaultRouter()
-router.register('patches', PatchViewSet, 'patch')
-router.register('people', PeopleViewSet, 'person')
-router.register('projects', ProjectViewSet, 'project')
-router.register('users', UserViewSet, 'user')
-
-patches_router = NestedSimpleRouter(router, r'patches', lookup='patch')
-patches_router.register(r'checks', CheckViewSet, base_name='patch-checks')
-- 
2.7.4



More information about the Patchwork mailing list