[PATCH v3 09/16] REST: Make 'Patch.state' editable

Andy Doan andy.doan at linaro.org
Tue Nov 29 09:04:01 AEDT 2016


On 11/25/2016 12:18 PM, Stephen Finucane wrote:
> The 'Patch.state' field is exposed by the API, but is not editable.
> Tests exist that suggest the field is editable, but they lie as they
> don't actually validate the field is changed (it hasn't). Make this
> field editable, using a custom field type to allow said user to submit a
> string representation of the state rather than an integer id.
>
> This has the side effect of changing the output representation of the
> 'state' field for the '/patches' endpoint to a slugified representation,
> i.e.:
>
>     "state": "under-review",
>
> Instead of:
>
>     "state": "Under review",
>
> The same slugified representation will be used in PATCH requests. This
> seems more consistent with how APIs generally do this stuff making it a
> "good thing" (TM).
>
> Signed-off-by: Stephen Finucane <stephen at that.guru>

Reviewed-by: Andy Doan <andy.doan at linaro.org>

> ---
> v3:
> - Rework test to demonstrate the prior non-editability of the field
>   (Daniel Axtens)
> - Use less confusing variable names in a list comprehensions (Andy
>   Doan)
> ---
>  patchwork/api/base.py            |  5 +++++
>  patchwork/api/patch.py           | 30 +++++++++++++++++++++++++-----
>  patchwork/tests/test_rest_api.py |  8 ++++++--
>  3 files changed, 36 insertions(+), 7 deletions(-)
>
> diff --git a/patchwork/api/base.py b/patchwork/api/base.py
> index dbd8148..13a8432 100644
> --- a/patchwork/api/base.py
> +++ b/patchwork/api/base.py
> @@ -23,6 +23,11 @@ from rest_framework import permissions
>  from rest_framework.pagination import PageNumberPagination
>  from rest_framework.response import Response
>
> +from patchwork.models import State
> +
> +STATE_CHOICES = ['-'.join(x.name.lower().split(' '))
> +                 for x in State.objects.all()]
> +
>
>  class LinkHeaderPagination(PageNumberPagination):
>      """Provide pagination based on rfc5988.
> diff --git a/patchwork/api/patch.py b/patchwork/api/patch.py
> index 8427450..f818f20 100644
> --- a/patchwork/api/patch.py
> +++ b/patchwork/api/patch.py
> @@ -20,25 +20,45 @@
>  import email.parser
>
>  from django.core.urlresolvers import reverse
> -from rest_framework.serializers import HyperlinkedModelSerializer
> +from rest_framework.exceptions import ValidationError
>  from rest_framework.generics import ListAPIView
>  from rest_framework.generics import RetrieveUpdateAPIView
> +from rest_framework.serializers import ChoiceField
> +from rest_framework.serializers import HyperlinkedModelSerializer
>  from rest_framework.serializers import SerializerMethodField
>
>  from patchwork.api.base import PatchworkPermission
> +from patchwork.api.base import STATE_CHOICES
>  from patchwork.models import Patch
> +from patchwork.models import State
> +
> +
> +class StateField(ChoiceField):
> +    """Avoid the need for a state endpoint."""
> +
> +    def __init__(self, *args, **kwargs):
> +        kwargs['choices'] = STATE_CHOICES
> +        super(StateField, self).__init__(*args, **kwargs)
> +
> +    def to_internal_value(self, data):
> +        data = ' '.join(data.split('-'))
> +        try:
> +            return State.objects.get(name__iexact=data)
> +        except State.DoesNotExist:
> +            raise ValidationError('Invalid state. Expected one of: %s ' %
> +                                  ', '.join(STATE_CHOICES))
> +
> +    def to_representation(self, obj):
> +        return '-'.join(obj.name.lower().split())
>
>
>  class PatchListSerializer(HyperlinkedModelSerializer):
>      mbox = SerializerMethodField()
> -    state = SerializerMethodField()
> +    state = StateField()
>      tags = SerializerMethodField()
>      check = SerializerMethodField()
>      checks = SerializerMethodField()
>
> -    def get_state(self, instance):
> -        return instance.state.name
> -
>      def get_mbox(self, instance):
>          request = self.context.get('request')
>          return request.build_absolute_uri(instance.get_mbox_url())
> diff --git a/patchwork/tests/test_rest_api.py b/patchwork/tests/test_rest_api.py
> index 469fd26..d764945 100644
> --- a/patchwork/tests/test_rest_api.py
> +++ b/patchwork/tests/test_rest_api.py
> @@ -31,6 +31,7 @@ from patchwork.tests.utils import create_maintainer
>  from patchwork.tests.utils import create_patch
>  from patchwork.tests.utils import create_person
>  from patchwork.tests.utils import create_project
> +from patchwork.tests.utils import create_state
>  from patchwork.tests.utils import create_user
>
>  if settings.ENABLE_REST_API:
> @@ -368,19 +369,22 @@ class TestPatchAPI(APITestCase):
>          # A maintainer can update
>          project = create_project()
>          patch = create_patch(project=project)
> +        state = create_state()
>          user = create_maintainer(project)
>          self.client.force_authenticate(user=user)
>
> +        self.assertNotEqual(Patch.objects.get(id=patch.id).state, state)
>          resp = self.client.patch(self.api_url(patch.id),
> -                                 {'state': 2})
> +                                 {'state': state.name})
>          self.assertEqual(status.HTTP_200_OK, resp.status_code)
> +        self.assertEqual(Patch.objects.get(id=patch.id).state, state)
>
>          # A normal user can't
>          user = create_user()
>          self.client.force_authenticate(user=user)
>
>          resp = self.client.patch(self.api_url(patch.id),
> -                                 {'state': 2})
> +                                 {'state': state.name})
>          self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
>
>      def test_delete(self):
>



More information about the Patchwork mailing list