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.
"""
|