[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