Skip to content

Commit 3d96133

Browse files
andrepapotivictor-accarini
authored andcommitted
api: add attention_set to patch
Users can now add/remove themselves from a patch attention list if they want to sinalize that they will look into it. To add themselves to the attention list they must execute a patch with a list with their IDs in the `attention_set` property. To remove themselves they must send a list with their negative IDs or an empty list. Maintainers can also remove anyone from the attention list by sending a list with negative IDs. Signed-off-by: andrepapoti <[email protected]> Signed-off-by: Victor Accarini <[email protected]>
1 parent de6b759 commit 3d96133

File tree

2 files changed

+97
-15
lines changed

2 files changed

+97
-15
lines changed

patchwork/api/patch.py

+96-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import email.parser
99

10+
from django.contrib.auth.models import User
1011
from django.core.exceptions import ValidationError
1112
from django.utils.text import slugify
1213
from django.utils.translation import gettext_lazy as _
@@ -15,6 +16,7 @@
1516
from rest_framework.generics import ListAPIView
1617
from rest_framework.generics import RetrieveUpdateAPIView
1718
from rest_framework.relations import RelatedField
19+
from rest_framework.response import Response
1820
from rest_framework.reverse import reverse
1921
from rest_framework.serializers import SerializerMethodField
2022
from rest_framework import status
@@ -28,6 +30,7 @@
2830
from patchwork.api.embedded import UserSerializer
2931
from patchwork.api.filters import PatchFilterSet
3032
from patchwork.models import Patch
33+
from patchwork.models import PatchAttentionSet
3134
from patchwork.models import PatchRelation
3235
from patchwork.models import State
3336
from patchwork.parser import clean_subject
@@ -76,12 +79,27 @@ class PatchConflict(APIException):
7679
)
7780

7881

82+
class PatchAttentionSetSerializer(BaseHyperlinkedModelSerializer):
83+
user = UserSerializer()
84+
85+
class Meta:
86+
model = PatchAttentionSet
87+
fields = [
88+
'user',
89+
'last_updated',
90+
]
91+
92+
7993
class PatchListSerializer(BaseHyperlinkedModelSerializer):
8094
web_url = SerializerMethodField()
8195
project = ProjectSerializer(read_only=True)
8296
state = StateField()
8397
submitter = PersonSerializer(read_only=True)
8498
delegate = UserSerializer(allow_null=True)
99+
attention_set = PatchAttentionSetSerializer(
100+
source='patchattentionset_set',
101+
many=True,
102+
)
85103
mbox = SerializerMethodField()
86104
series = SeriesSerializer(read_only=True)
87105
comments = SerializerMethodField()
@@ -170,6 +188,7 @@ class Meta:
170188
'hash',
171189
'submitter',
172190
'delegate',
191+
'attention_set',
173192
'mbox',
174193
'series',
175194
'comments',
@@ -201,6 +220,7 @@ class Meta:
201220
'list_archive_url',
202221
'related',
203222
),
223+
'1.4': ('attention_set',),
204224
}
205225
extra_kwargs = {
206226
'url': {'view_name': 'api-patch-detail'},
@@ -228,16 +248,7 @@ def get_headers(self, patch):
228248
def get_prefixes(self, instance):
229249
return clean_subject(instance.name)[1]
230250

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):
241252
# Validation rules
242253
# ----------------
243254
#
@@ -278,9 +289,7 @@ def update(self, instance, validated_data):
278289
if instance.related and instance.related.patches.count() == 2:
279290
instance.related.delete()
280291
instance.related = None
281-
return super(PatchDetailSerializer, self).update(
282-
instance, validated_data
283-
)
292+
return
284293

285294
# break before make
286295
relations = {patch.related for patch in patches if patch.related}
@@ -304,6 +313,14 @@ def update(self, instance, validated_data):
304313
instance.related = relation
305314
instance.save()
306315

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+
307324
return super(PatchDetailSerializer, self).update(
308325
instance, validated_data
309326
)
@@ -367,6 +384,7 @@ def get_queryset(self):
367384
'project',
368385
'series__project',
369386
'related__patches__project',
387+
'patchattentionset_set',
370388
)
371389
.select_related('state', 'submitter', 'series')
372390
.defer('content', 'diff', 'headers')
@@ -381,11 +399,16 @@ class PatchDetail(RetrieveUpdateAPIView):
381399
patch:
382400
Update a patch.
383401
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+
384408
put:
385409
Update a patch.
386410
"""
387411

388-
permission_classes = (PatchworkPermission,)
389412
serializer_class = PatchDetailSerializer
390413

391414
def get_queryset(self):
@@ -396,3 +419,62 @@ def get_queryset(self):
396419
'project', 'state', 'submitter', 'delegate', 'series'
397420
)
398421
)
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+
)

patchwork/tests/api/test_patch.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def test_list_bug_335(self):
238238
series = create_series()
239239
create_patches(5, series=series)
240240

241-
with self.assertNumQueries(5):
241+
with self.assertNumQueries(6):
242242
self.client.get(self.api_url())
243243

244244
@utils.store_samples('patch-detail')

0 commit comments

Comments
 (0)