[PATCH v3 02/16] REST: Create 'api' directory
Daniel Axtens
dja at axtens.net
Wed Nov 30 08:43:47 AEDT 2016
Stephen Finucane <stephen at that.guru> writes:
> 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)
Thanks!
Reviewed-by: Daniel Axtens <dja at axtens.net>
Regards,
Daniel
> 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
>
> _______________________________________________
> Patchwork mailing list
> Patchwork at lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/patchwork
More information about the Patchwork
mailing list