Skip to content

Commit

Permalink
Merge pull request #5 from maykinmedia/feature/create-list-endpoint
Browse files Browse the repository at this point in the history
Add endpoint for list creation
  • Loading branch information
SilviaAmAm authored May 3, 2024
2 parents bd918f9 + 8a07e49 commit 4a9b204
Show file tree
Hide file tree
Showing 10 changed files with 586 additions and 4 deletions.
2 changes: 1 addition & 1 deletion backend/src/openarchiefbeheer/accounts/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ class UserSerializer(serializers.ModelSerializer):

class Meta:
model = User
fields = ("username", "first_name", "last_name", "email", "role")
fields = ("pk", "username", "first_name", "last_name", "email", "role")
19 changes: 16 additions & 3 deletions backend/src/openarchiefbeheer/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@
SpectacularJSONAPIView,
SpectacularRedocView,
)
from rest_framework import routers

from openarchiefbeheer.accounts.api.views import ReviewersView
from openarchiefbeheer.destruction.api.viewsets import DestructionListViewSet

app_name = "api"

router = routers.DefaultRouter(trailing_slash=False)
router.register(r"destruction-lists", DestructionListViewSet)


urlpatterns = [
# API documentation
Expand Down Expand Up @@ -40,8 +45,16 @@
),
# Actual endpoints
path(
"v1/zaken/",
include("openarchiefbeheer.api.zaken.urls", namespace="zaken"),
"v1/",
include(
[
path(
"zaken/",
include("openarchiefbeheer.api.zaken.urls", namespace="zaken"),
),
path("reviewers/", ReviewersView.as_view(), name="reviewers"),
path("", include(router.urls)),
]
),
),
path("v1/reviewers/", ReviewersView.as_view(), name="reviewers"),
]
Empty file.
10 changes: 10 additions & 0 deletions backend/src/openarchiefbeheer/destruction/api/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.utils.translation import gettext_lazy as _

from rest_framework import permissions


class CanStartDestructionPermission(permissions.BasePermission):
message = _("You are not allowed to create a destruction list.")

def has_permission(self, request, view):
return request.user.role.can_start_destruction
80 changes: 80 additions & 0 deletions backend/src/openarchiefbeheer/destruction/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from django.db.models import Q
from django.utils.translation import gettext_lazy as _

from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from openarchiefbeheer.accounts.api.serializers import UserSerializer

from ..constants import ListItemStatus
from ..models import DestructionList, DestructionListAssignee, DestructionListItem


class DestructionListAssigneeSerializer(serializers.ModelSerializer):
class Meta:
model = DestructionListAssignee
fields = ("user", "order")


class DestructionListItemSerializer(serializers.ModelSerializer):
class Meta:
model = DestructionListItem
fields = (
"zaak",
"extra_zaak_data",
)

def validate(self, attrs: dict) -> dict:
if DestructionListItem.objects.filter(
Q(~Q(status=ListItemStatus.removed), zaak=attrs["zaak"])
).exists():
raise ValidationError(
{
"zaak": _(
"This case was already included in another destruction list and was not exempt during the review process."
)
}
)

return attrs


class DestructionListSerializer(serializers.ModelSerializer):
assignees = DestructionListAssigneeSerializer(many=True)
items = DestructionListItemSerializer(many=True)
author = UserSerializer(read_only=True)

class Meta:
model = DestructionList
fields = (
"name",
"author",
"contains_sensitive_info",
"assignees",
"items",
)

def create(self, validated_data: dict) -> DestructionList:
assignees_data = validated_data.pop("assignees")
items_data = validated_data.pop("items")

validated_data["author"] = self.context["request"].user
destruction_list = DestructionList.objects.create(**validated_data)

DestructionListItem.objects.bulk_create(
[
DestructionListItem(**{**item, "destruction_list": destruction_list})
for item in items_data
]
)
assignees = DestructionListAssignee.objects.bulk_create(
[
DestructionListAssignee(
**{**assignee, "destruction_list": destruction_list}
)
for assignee in assignees_data
]
)

destruction_list.assign(assignees[0])
return destruction_list
25 changes: 25 additions & 0 deletions backend/src/openarchiefbeheer/destruction/api/viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django.db import transaction

from rest_framework import mixins, viewsets
from rest_framework.permissions import IsAuthenticated

from ..models import DestructionList
from .permissions import CanStartDestructionPermission
from .serializers import DestructionListSerializer


class DestructionListViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = DestructionListSerializer
queryset = DestructionList.objects.all()

def get_permissions(self):
if self.action == "create":
permission_classes = [IsAuthenticated & CanStartDestructionPermission]
else:
permission_classes = [IsAuthenticated]
return [permission() for permission in permission_classes]

@transaction.atomic
def create(self, request, *args, **kwargs):
# TODO log creation
return super().create(request, *args, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 4.2.11 on 2024-05-01 14:25

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("destruction", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="DestructionListAssignee",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"order",
models.PositiveIntegerField(
db_index=True, editable=False, verbose_name="order"
),
),
(
"assigned_on",
models.DateTimeField(
blank=True, null=True, verbose_name="assigned on"
),
),
(
"destruction_list",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="assignees",
to="destruction.destructionlist",
verbose_name="destruction list",
),
),
(
"user",
models.ForeignKey(
help_text="The user assigned to the destruction list.",
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
verbose_name="user",
),
),
],
options={
"verbose_name": "destruction list assignee",
"verbose_name_plural": "destruction list assignees",
"ordering": ("order",),
"abstract": False,
"unique_together": {("destruction_list", "user")},
},
),
]
58 changes: 58 additions & 0 deletions backend/src/openarchiefbeheer/destruction/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from django.conf import settings
from django.core.mail import send_mail
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from ordered_model.models import OrderedModel

from openarchiefbeheer.destruction.constants import ListItemStatus, ListStatus


Expand Down Expand Up @@ -61,6 +66,10 @@ class Meta:
def __str__(self):
return self.name

@staticmethod
def assign(assignee: "DestructionListAssignee") -> None:
assignee.assign()


class DestructionListItem(models.Model):
destruction_list = models.ForeignKey(
Expand Down Expand Up @@ -96,3 +105,52 @@ class Meta:

def __str__(self):
return f"{self.destruction_list}: {self.zaak}"


class DestructionListAssignee(OrderedModel):
destruction_list = models.ForeignKey(
DestructionList,
on_delete=models.CASCADE,
related_name="assignees",
verbose_name=_("destruction list"),
)
user = models.ForeignKey(
"accounts.User",
on_delete=models.PROTECT,
verbose_name=_("user"),
help_text=_("The user assigned to the destruction list."),
)
assigned_on = models.DateTimeField(_("assigned on"), blank=True, null=True)

class Meta(OrderedModel.Meta):
verbose_name = _("destruction list assignee")
verbose_name_plural = _("destruction list assignees")
unique_together = ("destruction_list", "user")

def __str__(self):
return f"{self.user} ({self.destruction_list}, {self.order})"

def assign(self) -> None:
# TODO Log assignment
self.destruction_list.assignee = self.user
self.assigned_on = timezone.now()

self.destruction_list.save()
self.save()

self.notify()

# TODO refine what we want to do with notifications
def notify(self) -> None:
if not self.user.email:
return

is_reviewer = self.user != self.destruction_list.author
if is_reviewer:
send_mail(
_("Destruction list review request"),
_("There is a destruction list review request for you."),
settings.DEFAULT_FROM_EMAIL,
[self.user.email],
fail_silently=False,
)
Loading

0 comments on commit 4a9b204

Please sign in to comment.