[PATCH 01/13] REST: Create 'api' directory
Stephen Finucane
stephen at that.guru
Thu Nov 17 12:39:04 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.
Signed-off-by: Stephen Finucane <stephen at that.guru>
Cc: Andy Doan <andy.doan at linaro.org>
---
patchwork/api/__init__.py | 92 ++++++++++++++++++++++
patchwork/api/check.py | 96 +++++++++++++++++++++++
patchwork/api/patch.py | 84 ++++++++++++++++++++
patchwork/api/person.py | 37 +++++++++
patchwork/api/project.py | 60 +++++++++++++++
patchwork/api/user.py | 37 +++++++++
patchwork/rest_serializers.py | 144 ----------------------------------
patchwork/urls.py | 20 ++++-
patchwork/views/rest_api.py | 175 ------------------------------------------
9 files changed, 425 insertions(+), 320 deletions(-)
create mode 100644 patchwork/api/__init__.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..dc88a85
--- /dev/null
+++ b/patchwork/api/__init__.py
@@ -0,0 +1,92 @@
+# 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..f258cb2
--- /dev/null
+++ b/patchwork/api/check.py
@@ -0,0 +1,96 @@
+# 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 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
+ 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..e8b1903
--- /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 import PatchworkPermission
+from patchwork.api import PatchworkViewSet
+from patchwork.api 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..05b8a2f
--- /dev/null
+++ b/patchwork/api/person.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 patchwork.api import AuthenticatedReadOnly
+from patchwork.api import PatchworkViewSet
+from patchwork.api import URLSerializer
+from patchwork.models import Person
+
+
+class PersonSerializer(URLSerializer):
+ class Meta:
+ model = Person
+
+
+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..ea09acc
--- /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 import PatchworkPermission
+from patchwork.api 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..aa788b8
--- /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 import AuthenticatedReadOnly
+from patchwork.api 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 af6b1b7..0000000
--- a/patchwork/rest_serializers.py
+++ /dev/null
@@ -1,144 +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
-
-
-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
- read_only_fields = ('date', )
diff --git a/patchwork/urls.py b/patchwork/urls.py
index 33e4781..7644da9 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -146,7 +146,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