summaryrefslogtreecommitdiff
path: root/patchwork/api/bundle.py
blob: 93e323166d9ef7eed5d7bd2a9bf28d6921accfa2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# Patchwork - automated patch tracking system
# Copyright (C) 2017 Stephen Finucane <stephen@that.guru>
#
# SPDX-License-Identifier: GPL-2.0-or-later

from django.db.models import Q
from rest_framework import exceptions
from rest_framework.generics import ListCreateAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework import permissions
from rest_framework.serializers import SerializerMethodField
from rest_framework.serializers import ValidationError

from patchwork.api.base import BaseHyperlinkedModelSerializer
from patchwork.api.base import PatchworkPermission
from patchwork.api.filters import BundleFilterSet
from patchwork.api.embedded import PatchSerializer
from patchwork.api.embedded import ProjectSerializer
from patchwork.api.embedded import UserSerializer
from patchwork.api import utils
from patchwork.models import Bundle


class BundlePermission(permissions.BasePermission):
    """Ensure the API version, if configured, is >= v1.2.

    Bundle creation/updating was only added in API v1.2 and we don't want to
    change behavior in older API versions.
    """
    def has_permission(self, request, view):
        # read-only permission for everything
        if request.method in permissions.SAFE_METHODS:
            return True

        if not utils.has_version(request, '1.2'):
            raise exceptions.MethodNotAllowed(request.method)

        if request.method == 'POST' and (
                not request.user or not request.user.is_authenticated):
            return False

        # we have more to do but we can't do that until we have an object
        return True

    def has_object_permission(self, request, view, obj):
        if (request.user and
                request.user.is_authenticated and
                request.user == obj.owner):
            return True

        if not obj.public:
            # if the bundle isn't public, we don't want to leak the fact that
            # it exists
            raise exceptions.NotFound

        return request.method in permissions.SAFE_METHODS


class BundleSerializer(BaseHyperlinkedModelSerializer):

    web_url = SerializerMethodField()
    project = ProjectSerializer(read_only=True)
    mbox = SerializerMethodField()
    owner = UserSerializer(read_only=True)
    patches = PatchSerializer(many=True, required=True,
                              style={'base_template': 'input.html'})

    def get_web_url(self, instance):
        request = self.context.get('request')
        return request.build_absolute_uri(instance.get_absolute_url())

    def get_mbox(self, instance):
        request = self.context.get('request')
        return request.build_absolute_uri(instance.get_mbox_url())

    def create(self, validated_data):
        patches = validated_data.pop('patches')
        instance = super(BundleSerializer, self).create(validated_data)
        instance.overwrite_patches(patches)
        return instance

    def update(self, instance, validated_data):
        patches = validated_data.pop('patches', None)
        instance = super(BundleSerializer, self).update(
            instance, validated_data)
        if patches:
            instance.overwrite_patches(patches)
        return instance

    def validate_patches(self, value):
        if not len(value):
            raise ValidationError('Bundles cannot be empty')

        if len(set([p.project.id for p in value])) > 1:
            raise ValidationError('Bundle patches must belong to the same '
                                  'project')

        return value

    def validate(self, data):
        if data.get('patches'):
            data['project'] = data['patches'][0].project

        return super(BundleSerializer, self).validate(data)

    class Meta:
        model = Bundle
        fields = ('id', 'url', 'web_url', 'project', 'name', 'owner',
                  'patches', 'public', 'mbox')
        read_only_fields = ('project', 'owner', 'mbox')
        versioned_fields = {
            '1.1': ('web_url', ),
        }
        extra_kwargs = {
            'url': {'view_name': 'api-bundle-detail'},
        }


class BundleMixin(object):

    permission_classes = [PatchworkPermission & BundlePermission]
    serializer_class = BundleSerializer

    def get_queryset(self):
        if self.request.user.is_authenticated:
            bundle_filter = Q(owner=self.request.user) | Q(public=True)
        else:
            bundle_filter = Q(public=True)

        return Bundle.objects\
            .filter(bundle_filter)\
            .prefetch_related('patches',)\
            .select_related('owner', 'project')


class BundleList(BundleMixin, ListCreateAPIView):
    """List or create bundles."""

    filter_class = filterset_class = BundleFilterSet
    search_fields = ('name',)
    ordering_fields = ('id', 'name', 'owner')
    ordering = 'id'

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class BundleDetail(BundleMixin, RetrieveUpdateDestroyAPIView):
    """
    get:
    Show a bundle.

    patch:
    Update a bundle.

    put:
    Update a bundle.

    delete:
    Delete a bundle.
    """