Skip to content

Commit 00e654a

Browse files
authored
Merge pull request #136 from maykinmedia/feature/127-make-requested-changes
[#127] Create a response to a review
2 parents 4c20095 + 23e4428 commit 00e654a

File tree

8 files changed

+296
-95
lines changed

8 files changed

+296
-95
lines changed

backend/src/openarchiefbeheer/api/urls.py

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
DestructionListItemsViewSet,
1515
DestructionListReviewViewSet,
1616
DestructionListViewSet,
17+
ReviewResponseViewSet,
1718
)
1819
from openarchiefbeheer.zaken.api.views import (
1920
CacheZakenView,
@@ -41,6 +42,11 @@
4142
DestructionListItemReviewViewSet,
4243
basename="reviews-items",
4344
)
45+
router.register(
46+
r"review-responses",
47+
ReviewResponseViewSet,
48+
basename="review-responses",
49+
)
4450
router.register(r"zaken", ZakenViewSet, basename="zaken")
4551

4652

backend/src/openarchiefbeheer/destruction/api/filtersets.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
DestructionListItem,
88
DestructionListItemReview,
99
DestructionListReview,
10+
ReviewResponse,
1011
)
1112

1213

@@ -46,7 +47,12 @@ def filter_destruction_list_uuid(
4647

4748

4849
class DestructionListReviewItemFilterset(FilterSet):
49-
5050
class Meta:
5151
model = DestructionListItemReview
5252
fields = ("review",)
53+
54+
55+
class ReviewResponseFilterset(FilterSet):
56+
class Meta:
57+
model = ReviewResponse
58+
fields = ("review",)

backend/src/openarchiefbeheer/destruction/api/permissions.py

-16
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,3 @@ def has_object_permission(self, request, view, destruction_list):
3333
request.user == destruction_list.author
3434
and destruction_list.status == ListStatus.new
3535
)
36-
37-
38-
class CanMakeRequestedChanges(permissions.BasePermission):
39-
message = _(
40-
"You are either not allowed to update this destruction list or "
41-
"the destruction list can currently not be updated."
42-
)
43-
44-
def has_permission(self, request, view):
45-
return request.user.role and request.user.role.can_start_destruction
46-
47-
def has_object_permission(self, request, view, destruction_list):
48-
return (
49-
request.user == destruction_list.author
50-
and destruction_list.status == ListStatus.changes_requested
51-
)

backend/src/openarchiefbeheer/destruction/api/serializers.py

+52-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.db import transaction
12
from django.db.models import Q
23
from django.utils.translation import gettext_lazy as _
34

@@ -349,16 +350,64 @@ class ActionZaakSerializer(serializers.Serializer):
349350
required=False, help_text=_("A new date for when this case should be archived.")
350351
)
351352

353+
def to_internal_value(self, data: dict) -> dict:
354+
internal_value = super().to_internal_value(data)
355+
356+
if archiefactiedatum := internal_value.get("archiefactiedatum"):
357+
internal_value["archiefactiedatum"] = archiefactiedatum.isoformat()
358+
return internal_value
359+
352360

353361
class ReviewItemResponseSerializer(serializers.ModelSerializer):
354-
action_zaak = ActionZaakSerializer()
362+
action_zaak = ActionZaakSerializer(required=False)
355363

356364
class Meta:
357365
model = ReviewItemResponse
358-
fields = ("review_item", "action_item", "action_zaak", "created", "comment")
366+
fields = (
367+
"pk",
368+
"review_item",
369+
"action_item",
370+
"action_zaak",
371+
"created",
372+
"comment",
373+
)
359374

360375

361376
class ReviewResponseSerializer(serializers.ModelSerializer):
377+
items_responses = ReviewItemResponseSerializer(many=True)
378+
362379
class Meta:
363380
model = ReviewResponse
364-
fields = ("review", "comment", "created")
381+
fields = ("pk", "review", "comment", "created", "items_responses")
382+
383+
def validate(self, attrs: dict) -> dict:
384+
destruction_list = attrs["review"].destruction_list
385+
request = self.context["request"]
386+
387+
if not (
388+
request.user == destruction_list.author
389+
and destruction_list.status == ListStatus.changes_requested
390+
):
391+
raise ValidationError(
392+
_(
393+
"This user is either not allowed to update the destruction list or "
394+
"the destruction list cannot currently be updated."
395+
)
396+
)
397+
398+
return attrs
399+
400+
@transaction.atomic
401+
def create(self, validated_data: dict) -> ReviewResponse:
402+
items_responses_data = validated_data.pop("items_responses", [])
403+
items_responses = [
404+
ReviewItemResponse(**item_response)
405+
for item_response in items_responses_data
406+
]
407+
408+
review_response = ReviewResponse.objects.create(**validated_data)
409+
ReviewItemResponse.objects.bulk_create(items_responses)
410+
411+
# TODO kick off celery task to update the cases and to change the status/assignee of the destruction list
412+
413+
return review_response

backend/src/openarchiefbeheer/destruction/api/viewsets.py

+36-22
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,30 @@
44
from django_filters.rest_framework import DjangoFilterBackend
55
from drf_spectacular.utils import OpenApiExample, extend_schema, extend_schema_view
66
from rest_framework import mixins, viewsets
7-
from rest_framework.decorators import action
87
from rest_framework.permissions import IsAuthenticated
9-
from rest_framework.response import Response
108

119
from ..models import (
1210
DestructionList,
1311
DestructionListItem,
1412
DestructionListItemReview,
1513
DestructionListReview,
14+
ReviewResponse,
1615
)
1716
from .filtersets import (
1817
DestructionListFilterset,
1918
DestructionListItemFilterset,
2019
DestructionListReviewFilterset,
2120
DestructionListReviewItemFilterset,
21+
ReviewResponseFilterset,
2222
)
23-
from .permissions import (
24-
CanMakeRequestedChanges,
25-
CanStartDestructionPermission,
26-
CanUpdateDestructionList,
27-
)
23+
from .permissions import CanStartDestructionPermission, CanUpdateDestructionList
2824
from .serializers import (
2925
DestructionListAPIResponseSerializer,
3026
DestructionListItemReviewSerializer,
3127
DestructionListItemSerializer,
3228
DestructionListReviewSerializer,
3329
DestructionListSerializer,
30+
ReviewResponseSerializer,
3431
)
3532

3633

@@ -129,13 +126,6 @@
129126
description=_("Retrieve details about a destruction list."),
130127
responses={200: DestructionListAPIResponseSerializer},
131128
),
132-
make_requested_changes=extend_schema(
133-
tags=["Destruction list"],
134-
summary=_("Make requested changes"),
135-
description=_(
136-
"Update a destruction list after a reviewer has requested changes."
137-
),
138-
),
139129
)
140130
class DestructionListViewSet(
141131
mixins.RetrieveModelMixin,
@@ -155,8 +145,6 @@ def get_permissions(self):
155145
permission_classes = [IsAuthenticated & CanStartDestructionPermission]
156146
elif self.action == "update":
157147
permission_classes = [IsAuthenticated & CanUpdateDestructionList]
158-
elif self.action == "make_requested_changes":
159-
permission_classes = [IsAuthenticated & CanMakeRequestedChanges]
160148
else:
161149
permission_classes = [IsAuthenticated]
162150
return [permission() for permission in permission_classes]
@@ -174,12 +162,6 @@ def create(self, request, *args, **kwargs):
174162
def update(self, request, *args, **kwargs):
175163
return super().update(request, *args, **kwargs)
176164

177-
@action(detail=True, methods=["patch"], name="make-requested-changes")
178-
def make_requested_changes(self, request, *args, **kwargs):
179-
# Triggers the object permissions check
180-
self.get_object()
181-
return Response("Not implemented!")
182-
183165

184166
@extend_schema_view(
185167
list=extend_schema(
@@ -242,3 +224,35 @@ class DestructionListItemReviewViewSet(mixins.ListModelMixin, viewsets.GenericVi
242224
queryset = DestructionListItemReview.objects.all()
243225
filter_backends = (DjangoFilterBackend,)
244226
filterset_class = DestructionListReviewItemFilterset
227+
228+
229+
@extend_schema_view(
230+
list=extend_schema(
231+
tags=["Reviews"],
232+
summary=_("List review responses"),
233+
description=_("List all the responses to the reviews of a destruction list."),
234+
),
235+
create=extend_schema(
236+
tags=["Reviews"],
237+
summary=_("Create a review response"),
238+
description=_(
239+
"Create a response to a review. "
240+
"You need to be the author of a destruction list for this and you need to be assigned to it. "
241+
"The status of the destruction list must be 'changes requested'."
242+
),
243+
),
244+
)
245+
class ReviewResponseViewSet(
246+
mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet
247+
):
248+
serializer_class = ReviewResponseSerializer
249+
queryset = ReviewResponse.objects.all()
250+
filter_backends = (DjangoFilterBackend,)
251+
filterset_class = ReviewResponseFilterset
252+
253+
def get_permissions(self):
254+
if self.action == "create":
255+
permission_classes = [IsAuthenticated & CanStartDestructionPermission]
256+
else:
257+
permission_classes = [IsAuthenticated]
258+
return [permission() for permission in permission_classes]

backend/src/openarchiefbeheer/destruction/models.py

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import uuid as _uuid
33

44
from django.db import models
5+
from django.db.models import QuerySet
56
from django.utils import timezone
67
from django.utils.translation import gettext_lazy as _
78

@@ -323,6 +324,10 @@ class Meta:
323324
def __str__(self):
324325
return f"Response to {self.review}"
325326

327+
@property
328+
def items_responses(self) -> QuerySet["ReviewItemResponse"]:
329+
return ReviewItemResponse.objects.filter(review_item__review=self.review)
330+
326331

327332
class ReviewItemResponse(models.Model):
328333
review_item = models.ForeignKey(

backend/src/openarchiefbeheer/destruction/tests/factories.py

+14
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,17 @@ class DestructionListItemReviewFactory(factory.django.DjangoModelFactory):
4747

4848
class Meta:
4949
model = "destruction.DestructionListItemReview"
50+
51+
52+
class ReviewItemResponseFactory(factory.django.DjangoModelFactory):
53+
review_item = factory.SubFactory(DestructionListItemReviewFactory)
54+
55+
class Meta:
56+
model = "destruction.ReviewItemResponse"
57+
58+
59+
class ReviewResponseFactory(factory.django.DjangoModelFactory):
60+
review = factory.SubFactory(DestructionListReviewFactory)
61+
62+
class Meta:
63+
model = "destruction.ReviewResponse"

0 commit comments

Comments
 (0)