Skip to content

Commit

Permalink
Merge pull request #513 from maykinmedia/feature/498-show-not-reviewe…
Browse files Browse the repository at this point in the history
…d-zaken

[#498] Add field to Destruction list item serialiser
  • Loading branch information
SilviaAmAm authored Nov 22, 2024
2 parents 3398cb3 + 4d84ba5 commit 57e691a
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 7 deletions.
15 changes: 14 additions & 1 deletion backend/src/openarchiefbeheer/destruction/api/filtersets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.db.models import Case, QuerySet, Value, When
from django.db.models import Case, F, QuerySet, Value, When
from django.utils.translation import gettext_lazy as _

from django_filters import (
Expand Down Expand Up @@ -41,6 +41,13 @@ class DestructionListItemFilterset(FilterSet):
"to define the exact same ordering as the zaak endpoint"
),
)
order_review_ignored = BooleanFilter(
field_name="ordering",
method="filter_order_review_ignored",
help_text=_(
"Return the items where a record manager went against the advice of a reviewer first."
),
)

class Meta:
model = DestructionListItem
Expand All @@ -49,6 +56,7 @@ class Meta:
"status",
"processing_status",
"order_match_zaken",
"order_review_ignored",
)

def filter_in_destruction_list(
Expand Down Expand Up @@ -77,6 +85,11 @@ def filter_order_match_zaken(
) -> QuerySet[DestructionListItem]:
return queryset.order_by("zaak__pk")

def filter_order_review_ignored(
self, queryset: QuerySet[DestructionListItem], name: str, value: bool
) -> QuerySet[DestructionListItem]:
return queryset.order_by(F("review_advice_ignored").desc(nulls_last=True))


class DestructionListFilterset(FilterSet):
assignee = NumberFilter(
Expand Down
26 changes: 25 additions & 1 deletion backend/src/openarchiefbeheer/destruction/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from django.db.models import F, Q, QuerySet
from django.utils.translation import gettext_lazy as _

from drf_spectacular.utils import extend_schema_field
from drf_spectacular.plumbing import build_basic_type
from drf_spectacular.utils import OpenApiTypes, extend_schema_field
from requests.exceptions import HTTPError
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
Expand Down Expand Up @@ -238,6 +239,13 @@ def validate(self, attrs: dict) -> dict:

class DestructionListItemReadSerializer(serializers.ModelSerializer):
zaak = ZaakSerializer(allow_null=True, read_only=True)
review_advice_ignored = serializers.SerializerMethodField(
help_text=_(
"Specifies whether the record manager went against"
" the advice of a reviewer when processing the last review."
),
allow_null=True,
)

class Meta:
model = DestructionListItem
Expand All @@ -247,8 +255,24 @@ class Meta:
"extra_zaak_data",
"zaak",
"processing_status",
"review_advice_ignored",
)

@extend_schema_field(build_basic_type(OpenApiTypes.BOOL))
def get_review_advice_ignored(self, item: DestructionListItem) -> bool | None:
if hasattr(item, "review_advice_ignored"):
return item.review_advice_ignored

last_review_response = (
ReviewItemResponse.objects.filter(review_item__destruction_list_item=item)
.order_by("-created")
.last()
)
if last_review_response is None:
return

return last_review_response.action_item == DestructionListItemAction.keep


class DestructionListWriteSerializer(serializers.ModelSerializer):
add = DestructionListItemWriteSerializer(many=True, required=False)
Expand Down
42 changes: 39 additions & 3 deletions backend/src/openarchiefbeheer/destruction/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models import Prefetch, QuerySet
from django.db.models import Case, OuterRef, Prefetch, QuerySet, Subquery, Value, When
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _

Expand All @@ -19,13 +19,19 @@
from openarchiefbeheer.utils.paginators import PageNumberPagination
from openarchiefbeheer.zaken.api.filtersets import ZaakFilterSet

from ..constants import WAITING_PERIOD, InternalStatus, ListRole
from ..constants import (
WAITING_PERIOD,
DestructionListItemAction,
InternalStatus,
ListRole,
)
from ..models import (
DestructionList,
DestructionListAssignee,
DestructionListItem,
DestructionListItemReview,
DestructionListReview,
ReviewItemResponse,
ReviewResponse,
)
from ..tasks import delete_destruction_list
Expand Down Expand Up @@ -394,14 +400,44 @@ class DestructionListItemsViewSet(
viewsets.GenericViewSet,
):
serializer_class = DestructionListItemReadSerializer
queryset = DestructionListItem.objects.all().select_related("zaak")
filter_backends = (NestedFilterBackend,)
filterset_class = DestructionListItemFilterset
filterset_kwargs = {"prefix": "item"}
nested_filterset_class = ZaakFilterSet
nested_filterset_relation_field = "zaak"
pagination_class = PageNumberPagination

def get_queryset(self):
review_response_items = ReviewItemResponse.objects.filter(
review_item__destruction_list_item=OuterRef("pk")
).order_by("-created")

qs = (
DestructionListItem.objects.all()
.select_related("zaak")
.annotate(
last_review_response_action_item=Subquery(
review_response_items.values("action_item")[:1]
)
)
.annotate(
review_advice_ignored=Case(
When(
last_review_response_action_item=DestructionListItemAction.keep,
then=Value(True),
),
When(
last_review_response_action_item=DestructionListItemAction.remove,
then=Value(False),
),
When(
last_review_response_action_item__isnull=True, then=Value(None)
),
)
)
)
return qs


@extend_schema_view(
list=extend_schema(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@

from openarchiefbeheer.accounts.tests.factories import UserFactory

from ...constants import InternalStatus, ListItemStatus
from ..factories import DestructionListFactory, DestructionListItemFactory
from ...constants import (
DestructionListItemAction,
InternalStatus,
ListItemStatus,
ListStatus,
)
from ..factories import (
DestructionListFactory,
DestructionListItemFactory,
DestructionListItemReviewFactory,
DestructionListReviewFactory,
ReviewItemResponseFactory,
)


class DestructionListItemsViewSetTest(APITestCase):
Expand Down Expand Up @@ -221,3 +232,50 @@ def test_item_with_extra_zaak_data(self):
data["results"][0]["extraZaakData"]["url"],
"http://localhost:8003/zaken/api/v1/zaken/eafc5f37-4524-43ce-872f-39ff3df11e1e",
)

def test_retrieve_items_with_review_responses(self):
record_manager = UserFactory.create(post__can_start_destruction=True)
review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.changes_requested,
)
item_reviews = DestructionListItemReviewFactory.create_batch(
3,
destruction_list_item__destruction_list=review.destruction_list,
review=review,
)
ReviewItemResponseFactory.create(
review_item=item_reviews[1],
review_item__review=review,
action_item=DestructionListItemAction.keep,
)
ReviewItemResponseFactory.create(
review_item=item_reviews[2],
review_item__review=review,
action_item=DestructionListItemAction.remove,
)

self.client.force_authenticate(user=record_manager)
endpoint = furl(reverse("api:destruction-list-items-list"))
endpoint.args["item-order_review_ignored"] = True

response = self.client.get(
endpoint.url,
)

self.assertEqual(response.status_code, status.HTTP_200_OK)

data = response.json()

self.assertEqual(
data["results"][0]["pk"], item_reviews[1].destruction_list_item.pk
)
self.assertTrue(data["results"][0]["reviewAdviceIgnored"])
self.assertEqual(
data["results"][1]["pk"], item_reviews[2].destruction_list_item.pk
)
self.assertFalse(data["results"][1]["reviewAdviceIgnored"])
self.assertEqual(
data["results"][2]["pk"], item_reviews[0].destruction_list_item.pk
)
self.assertIsNone(data["results"][2]["reviewAdviceIgnored"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from django.test import TestCase

from openarchiefbeheer.accounts.tests.factories import UserFactory

from ...api.serializers import DestructionListItemReadSerializer
from ...constants import DestructionListItemAction, ListStatus
from ..factories import (
DestructionListItemReviewFactory,
DestructionListReviewFactory,
ReviewItemResponseFactory,
)


class TestDestructionListItemSerializer(TestCase):
def test_ignored_review_advice(self):
record_manager = UserFactory.create(post__can_start_destruction=True)
review = DestructionListReviewFactory.create(
destruction_list__author=record_manager,
destruction_list__status=ListStatus.changes_requested,
)
item_reviews = DestructionListItemReviewFactory.create_batch(
3,
destruction_list_item__destruction_list=review.destruction_list,
review=review,
)
ReviewItemResponseFactory.create(
review_item=item_reviews[1],
review_item__review=review,
action_item=DestructionListItemAction.keep,
)
ReviewItemResponseFactory.create(
review_item=item_reviews[2],
review_item__review=review,
action_item=DestructionListItemAction.remove,
)

with self.subTest("Accepted item"):
serialiser = DestructionListItemReadSerializer(
instance=item_reviews[0].destruction_list_item
)

self.assertIsNone(serialiser.data["review_advice_ignored"])

with self.subTest("Item with ignored feedback"):
serialiser = DestructionListItemReadSerializer(
instance=item_reviews[1].destruction_list_item
)

self.assertTrue(serialiser.data["review_advice_ignored"])

with self.subTest("Item with accepted feedback"):
serialiser = DestructionListItemReadSerializer(
instance=item_reviews[2].destruction_list_item
)

self.assertFalse(serialiser.data["review_advice_ignored"])

0 comments on commit 57e691a

Please sign in to comment.