Hi Stephen, Apologies for the reviews on the old version. Those comments still apply, I think.
With this patch, I think the commit message has somehow got munged? It doesn't seem to bear any resemblence to the actual patch. Regards, Daniel > We still care about Django 1.6 and 1.7, at least until 2.0 is released. > Start testing REST functionality on these versions by using older > versions. > > Signed-off-by: Stephen Finucane <[email protected]> > --- > v2: > - Rebase onto master > --- > patchwork/api/__init__.py | 92 ++++++++++++++++++++++ > 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 > ------------------------------------------ > 9 files changed, 428 insertions(+), 323 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..12706be > --- /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 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..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..9a97dbb > --- /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 import AuthenticatedReadOnly > +from patchwork.api import PatchworkViewSet > +from patchwork.api 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..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 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 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 > > _______________________________________________ > Patchwork mailing list > [email protected] > https://lists.ozlabs.org/listinfo/patchwork _______________________________________________ Patchwork mailing list [email protected] https://lists.ozlabs.org/listinfo/patchwork
