7
7
8
8
import email .parser
9
9
10
+ from django .contrib .auth .models import User
10
11
from django .core .exceptions import ValidationError
11
12
from django .utils .text import slugify
12
13
from django .utils .translation import gettext_lazy as _
15
16
from rest_framework .generics import ListAPIView
16
17
from rest_framework .generics import RetrieveUpdateAPIView
17
18
from rest_framework .relations import RelatedField
19
+ from rest_framework .response import Response
18
20
from rest_framework .reverse import reverse
19
21
from rest_framework .serializers import SerializerMethodField
20
22
from rest_framework import status
28
30
from patchwork .api .embedded import UserSerializer
29
31
from patchwork .api .filters import PatchFilterSet
30
32
from patchwork .models import Patch
33
+ from patchwork .models import PatchAttentionSet
31
34
from patchwork .models import PatchRelation
32
35
from patchwork .models import State
33
36
from patchwork .parser import clean_subject
@@ -76,12 +79,27 @@ class PatchConflict(APIException):
76
79
)
77
80
78
81
82
+ class PatchAttentionSetSerializer (BaseHyperlinkedModelSerializer ):
83
+ user = UserSerializer ()
84
+
85
+ class Meta :
86
+ model = PatchAttentionSet
87
+ fields = [
88
+ 'user' ,
89
+ 'last_updated' ,
90
+ ]
91
+
92
+
79
93
class PatchListSerializer (BaseHyperlinkedModelSerializer ):
80
94
web_url = SerializerMethodField ()
81
95
project = ProjectSerializer (read_only = True )
82
96
state = StateField ()
83
97
submitter = PersonSerializer (read_only = True )
84
98
delegate = UserSerializer (allow_null = True )
99
+ attention_set = PatchAttentionSetSerializer (
100
+ source = 'patchattentionset_set' ,
101
+ many = True ,
102
+ )
85
103
mbox = SerializerMethodField ()
86
104
series = SeriesSerializer (read_only = True )
87
105
comments = SerializerMethodField ()
@@ -170,6 +188,7 @@ class Meta:
170
188
'hash' ,
171
189
'submitter' ,
172
190
'delegate' ,
191
+ 'attention_set' ,
173
192
'mbox' ,
174
193
'series' ,
175
194
'comments' ,
@@ -201,6 +220,7 @@ class Meta:
201
220
'list_archive_url' ,
202
221
'related' ,
203
222
),
223
+ '1.4' : ('attention_set' ,),
204
224
}
205
225
extra_kwargs = {
206
226
'url' : {'view_name' : 'api-patch-detail' },
@@ -228,16 +248,7 @@ def get_headers(self, patch):
228
248
def get_prefixes (self , instance ):
229
249
return clean_subject (instance .name )[1 ]
230
250
231
- def update (self , instance , validated_data ):
232
- # d-r-f cannot handle writable nested models, so we handle that
233
- # specifically ourselves and let d-r-f handle the rest
234
- if 'related' not in validated_data :
235
- return super (PatchDetailSerializer , self ).update (
236
- instance , validated_data
237
- )
238
-
239
- related = validated_data .pop ('related' )
240
-
251
+ def update_related (self , instance , related ):
241
252
# Validation rules
242
253
# ----------------
243
254
#
@@ -278,9 +289,7 @@ def update(self, instance, validated_data):
278
289
if instance .related and instance .related .patches .count () == 2 :
279
290
instance .related .delete ()
280
291
instance .related = None
281
- return super (PatchDetailSerializer , self ).update (
282
- instance , validated_data
283
- )
292
+ return
284
293
285
294
# break before make
286
295
relations = {patch .related for patch in patches if patch .related }
@@ -304,6 +313,14 @@ def update(self, instance, validated_data):
304
313
instance .related = relation
305
314
instance .save ()
306
315
316
+ def update (self , instance , validated_data ):
317
+ # d-r-f cannot handle writable nested models, so we handle that
318
+ # specifically ourselves and let d-r-f handle the rest
319
+
320
+ if 'related' in validated_data :
321
+ related = validated_data .pop ('related' )
322
+ self .update_related (instance , related )
323
+
307
324
return super (PatchDetailSerializer , self ).update (
308
325
instance , validated_data
309
326
)
@@ -367,6 +384,7 @@ def get_queryset(self):
367
384
'project' ,
368
385
'series__project' ,
369
386
'related__patches__project' ,
387
+ 'patchattentionset_set' ,
370
388
)
371
389
.select_related ('state' , 'submitter' , 'series' )
372
390
.defer ('content' , 'diff' , 'headers' )
@@ -381,11 +399,16 @@ class PatchDetail(RetrieveUpdateAPIView):
381
399
patch:
382
400
Update a patch.
383
401
402
+ Users can set their intention to review or comment about a patch using the
403
+ `attention_set` property. Users can set their intentions by adding their
404
+ IDs or its negative value to the list. Maintainers can remove people from
405
+ the list but only a user can add itself.
406
+
407
+
384
408
put:
385
409
Update a patch.
386
410
"""
387
411
388
- permission_classes = (PatchworkPermission ,)
389
412
serializer_class = PatchDetailSerializer
390
413
391
414
def get_queryset (self ):
@@ -396,3 +419,62 @@ def get_queryset(self):
396
419
'project' , 'state' , 'submitter' , 'delegate' , 'series'
397
420
)
398
421
)
422
+
423
+ def partial_update (self , request , * args , ** kwargs ):
424
+ obj = self .get_object ()
425
+ req_user_id = request .user .id
426
+ is_maintainer = request .user .is_authenticated and (
427
+ obj .project in request .user .profile .maintainer_projects .all ()
428
+ )
429
+
430
+ if 'attention_set' in request .data and request .method in ('PATCH' ,):
431
+ attention_set = request .data .get ('attention_set' , None )
432
+ del request .data ['attention_set' ]
433
+ removal_list = [
434
+ - user_id for user_id in set (attention_set ) if user_id < 0
435
+ ]
436
+ addition_list = [
437
+ user_id for user_id in set (attention_set ) if user_id > 0
438
+ ]
439
+
440
+ if not addition_list and not removal_list :
441
+ removal_list = [req_user_id ]
442
+
443
+ if len (addition_list ) > 1 or (
444
+ addition_list and req_user_id not in addition_list
445
+ ):
446
+ raise PermissionDenied (
447
+ detail = "Only the user can declare it's own intention of "
448
+ 'reviewing a patch'
449
+ )
450
+
451
+ if not is_maintainer :
452
+ if removal_list and req_user_id not in removal_list :
453
+ raise PermissionDenied (
454
+ detail = "Only the user can remove it's own "
455
+ 'intention of reviewing a patch'
456
+ )
457
+
458
+ try :
459
+ if addition_list :
460
+ PatchAttentionSet .objects .upsert (obj , addition_list )
461
+ if removal_list :
462
+ PatchAttentionSet .objects .soft_delete (
463
+ obj , removal_list , reason = f'removed by { request .user } '
464
+ )
465
+ except User .DoesNotExist :
466
+ return Response (
467
+ {'message' : 'Unable to find referenced user' },
468
+ status = 404 ,
469
+ )
470
+
471
+ if not is_maintainer :
472
+ serializer = self .get_serializer (obj )
473
+ return Response (
474
+ serializer .data ,
475
+ status = 200 ,
476
+ )
477
+
478
+ return super (PatchDetail , self ).partial_update (
479
+ request , * args , ** kwargs
480
+ )
0 commit comments