From: Stephen Finucane Date: Mon, 31 Oct 2016 18:23:29 +0000 (+0000) Subject: REST: Create 'api' directory X-Git-Tag: v2.0.0-rc1~128 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4f936cefabc77772c20b11bc2b741ece2c66ea72;p=thirdparty%2Fpatchwork.git REST: Create 'api' directory 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 Reviewed-by: Daniel Axtens --- diff --git a/patchwork/api/__init__.py b/patchwork/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/patchwork/api/base.py b/patchwork/api/base.py new file mode 100644 index 00000000..7333a7f2 --- /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 00000000..31ade075 --- /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 00000000..c36a11bf --- /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 00000000..b1168f74 --- /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 00000000..7def2ed9 --- /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 00000000..b25054fe --- /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 7bbad8d4..00000000 --- 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 98b4dbec..66718376 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 9c586697..00000000 --- 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')