Skip to content

Commit bd918f9

Browse files
authored
Merge pull request #2 from maykinmedia/feature/destruction-list-endpoints
Endpoint to retrieve zaken
2 parents abe7869 + 565116c commit bd918f9

File tree

13 files changed

+452
-0
lines changed

13 files changed

+452
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from furl import furl
2+
from requests_mock import Mocker
3+
from rest_framework import status
4+
from rest_framework.reverse import reverse
5+
from rest_framework.test import APITestCase
6+
from zgw_consumers.constants import APITypes
7+
from zgw_consumers.test.factories import ServiceFactory
8+
9+
from openarchiefbeheer.accounts.tests.factories import UserFactory
10+
11+
RESPONSE_LIST = {
12+
"results": [
13+
{
14+
"identificatie": "ZAAK-01",
15+
"url": "http://zaken-api.nl/zaken/api/v1/zaken/111-111-111",
16+
},
17+
{
18+
"identificatie": "ZAAK-02",
19+
"url": "http://zaken-api.nl/zaken/api/v1/zaken/222-222-222",
20+
},
21+
],
22+
"count": 2,
23+
"previous": None,
24+
"next": None,
25+
}
26+
27+
28+
@Mocker()
29+
class ZakenEndpointsTestCase(APITestCase):
30+
@classmethod
31+
def setUpTestData(cls):
32+
super().setUpTestData()
33+
34+
ServiceFactory.create(
35+
api_type=APITypes.zrc,
36+
api_root="http://localhost:8003/zaken/api/v1",
37+
client_id="vcr-local-test",
38+
secret="vcr-local-test",
39+
)
40+
41+
def test_not_authenticated(self, m):
42+
api_url = reverse("api:zaken:zaken")
43+
44+
response = self.client.get(api_url)
45+
46+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
47+
48+
def test_successful_response(self, m):
49+
user = UserFactory.create(username="test", password="password")
50+
api_url = reverse("api:zaken:zaken")
51+
52+
m.get("http://localhost:8003/zaken/api/v1/zaken", json=RESPONSE_LIST)
53+
54+
self.client.force_authenticate(user=user)
55+
response = self.client.get(api_url)
56+
57+
self.assertEqual(response.status_code, status.HTTP_200_OK)
58+
59+
data = response.json()
60+
61+
self.assertEqual(data, RESPONSE_LIST)
62+
63+
def test_with_filters(self, m):
64+
user = UserFactory.create(username="test", password="password")
65+
api_url = furl(reverse("api:zaken:zaken"))
66+
api_url.args["bronorganisatie"] = "000000"
67+
api_url.args["archiefstatus"] = "nog_te_archiveren"
68+
69+
m.get("http://localhost:8003/zaken/api/v1/zaken", json=RESPONSE_LIST)
70+
71+
self.client.force_authenticate(user=user)
72+
response = self.client.get(api_url.url)
73+
74+
self.assertEqual(response.status_code, status.HTTP_200_OK)
75+
76+
query_params = m.request_history[0].qs
77+
78+
self.assertIn("bronorganisatie", query_params)
79+
self.assertIn("archiefstatus", query_params)
80+
81+
def test_error_response(self, m):
82+
user = UserFactory.create(username="test", password="password")
83+
api_url = reverse("api:zaken:zaken")
84+
85+
m.get(
86+
"http://localhost:8003/zaken/api/v1/zaken",
87+
json={"invalid": "Something went wrong"},
88+
status_code=status.HTTP_400_BAD_REQUEST,
89+
)
90+
91+
self.client.force_authenticate(user=user)
92+
response = self.client.get(api_url)
93+
94+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
95+
96+
data = response.json()
97+
98+
self.assertEqual(data, {"error": {"invalid": "Something went wrong"}})

backend/src/openarchiefbeheer/api/urls.py

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
app_name = "api"
1212

13+
1314
urlpatterns = [
1415
# API documentation
1516
path(
@@ -37,5 +38,10 @@
3738
"openarchiefbeheer.api.authentication.urls", namespace="authentication"
3839
),
3940
),
41+
# Actual endpoints
42+
path(
43+
"v1/zaken/",
44+
include("openarchiefbeheer.api.zaken.urls", namespace="zaken"),
45+
),
4046
path("v1/reviewers/", ReviewersView.as_view(), name="reviewers"),
4147
]

backend/src/openarchiefbeheer/api/zaken/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.urls import path
2+
3+
from .views import ZakenView
4+
5+
app_name = "zaken"
6+
7+
urlpatterns = [
8+
path("", ZakenView.as_view(), name="zaken"),
9+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from django.utils.translation import gettext_lazy as _
2+
3+
from drf_spectacular.utils import extend_schema
4+
from requests.exceptions import RequestException
5+
from rest_framework.renderers import JSONRenderer
6+
from rest_framework.request import Request
7+
from rest_framework.response import Response
8+
from rest_framework.views import APIView
9+
from zgw_consumers.client import build_client
10+
from zgw_consumers.constants import APITypes
11+
from zgw_consumers.models import Service
12+
13+
14+
@extend_schema(
15+
summary=_("List zaken"),
16+
description=_(
17+
"Retrieve zaken using the configured ZRC service. "
18+
"For information over the query parameters accepted and the schema of the response, look at the "
19+
"'/zaken/api/v1/zaken' list endpoint of Open Zaak."
20+
),
21+
)
22+
class ZakenView(APIView):
23+
# We don't want the CamelCase renderer to alter the responses from Open Zaak
24+
renderer_classes = (JSONRenderer,)
25+
26+
def get(self, request: Request) -> Response:
27+
zrc_service = Service.objects.get(api_type=APITypes.zrc)
28+
zrc_client = build_client(zrc_service)
29+
30+
with zrc_client:
31+
response = zrc_client.get(
32+
"zaken",
33+
headers={"Accept-Crs": "EPSG:4326"},
34+
params=request.query_params,
35+
)
36+
37+
try:
38+
response.raise_for_status()
39+
except RequestException:
40+
return Response({"error": response.json()}, status=response.status_code)
41+
42+
return Response(response.json(), status=response.status_code)

backend/src/openarchiefbeheer/conf/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"simple_certmanager",
122122
# Project applications.
123123
"openarchiefbeheer.accounts",
124+
"openarchiefbeheer.destruction",
124125
"openarchiefbeheer.utils",
125126
]
126127

backend/src/openarchiefbeheer/destruction/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.db import models
2+
from django.utils.translation import gettext_lazy as _
3+
4+
5+
class ListStatus(models.TextChoices):
6+
in_progress = "in_progress", _("in progress")
7+
processing = "processing", _("processing")
8+
completed = "completed", _("completed")
9+
10+
11+
class ListItemStatus(models.TextChoices):
12+
suggested = "suggested", _("suggested for destruction")
13+
removed = "removed", _("removed from the destruction list during review")
14+
processing = "processing", _("is currently being destroyed")
15+
destroyed = "destroyed", _("successfully destroyed")
16+
failed = "failed", _("destruction did not succeed")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Generated by Django 4.2.11 on 2024-05-01 10:48
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name="DestructionList",
19+
fields=[
20+
(
21+
"id",
22+
models.AutoField(
23+
auto_created=True,
24+
primary_key=True,
25+
serialize=False,
26+
verbose_name="ID",
27+
),
28+
),
29+
(
30+
"name",
31+
models.CharField(max_length=200, unique=True, verbose_name="name"),
32+
),
33+
("created", models.DateTimeField(auto_now_add=True)),
34+
(
35+
"end",
36+
models.DateTimeField(
37+
blank=True,
38+
help_text="The timestamp at which all the cases in the list have been deleted.",
39+
null=True,
40+
verbose_name="end",
41+
),
42+
),
43+
(
44+
"contains_sensitive_info",
45+
models.BooleanField(
46+
default=True,
47+
help_text="Specify whether this destruction list contains privacy sensitive data. If set to true, the report of destruction will NOT contain case descriptions or the remarks by the reviewers.",
48+
verbose_name="contains sensitive information",
49+
),
50+
),
51+
(
52+
"status",
53+
models.CharField(
54+
choices=[
55+
("in_progress", "in progress"),
56+
("processing", "processing"),
57+
("completed", "completed"),
58+
],
59+
default="in_progress",
60+
max_length=80,
61+
verbose_name="status",
62+
),
63+
),
64+
(
65+
"zaak_destruction_report_url",
66+
models.URLField(
67+
blank=True,
68+
help_text="The URL of the case containing the destruction report for this destruction list.",
69+
verbose_name="zaak destruction report URL",
70+
),
71+
),
72+
(
73+
"assignee",
74+
models.ForeignKey(
75+
blank=True,
76+
help_text="Currently assigned user.",
77+
null=True,
78+
on_delete=django.db.models.deletion.SET_NULL,
79+
related_name="assigned_lists",
80+
to=settings.AUTH_USER_MODEL,
81+
verbose_name="assignee",
82+
),
83+
),
84+
(
85+
"author",
86+
models.ForeignKey(
87+
help_text="Creator of destruction list.",
88+
on_delete=django.db.models.deletion.CASCADE,
89+
related_name="created_lists",
90+
to=settings.AUTH_USER_MODEL,
91+
verbose_name="author",
92+
),
93+
),
94+
],
95+
options={
96+
"verbose_name": "destruction list",
97+
"verbose_name_plural": "destruction lists",
98+
},
99+
),
100+
migrations.CreateModel(
101+
name="DestructionListItem",
102+
fields=[
103+
(
104+
"id",
105+
models.AutoField(
106+
auto_created=True,
107+
primary_key=True,
108+
serialize=False,
109+
verbose_name="ID",
110+
),
111+
),
112+
(
113+
"zaak",
114+
models.URLField(
115+
db_index=True,
116+
help_text="URL-reference to the ZAAK (in Zaken API), which is planned to be destroyed.",
117+
verbose_name="zaak",
118+
),
119+
),
120+
(
121+
"status",
122+
models.CharField(
123+
choices=[
124+
("suggested", "suggested for destruction"),
125+
(
126+
"removed",
127+
"removed from the destruction list during review",
128+
),
129+
("processing", "is currently being destroyed"),
130+
("destroyed", "successfully destroyed"),
131+
("failed", "destruction did not succeed"),
132+
],
133+
default="suggested",
134+
max_length=80,
135+
verbose_name="status",
136+
),
137+
),
138+
(
139+
"extra_zaak_data",
140+
models.JSONField(
141+
blank=True,
142+
help_text="Additional information of the zaak",
143+
null=True,
144+
verbose_name="extra zaak data",
145+
),
146+
),
147+
(
148+
"destruction_list",
149+
models.ForeignKey(
150+
on_delete=django.db.models.deletion.CASCADE,
151+
related_name="items",
152+
to="destruction.destructionlist",
153+
verbose_name="destruction list",
154+
),
155+
),
156+
],
157+
options={
158+
"verbose_name": "destruction list item",
159+
"verbose_name_plural": "destruction list items",
160+
"unique_together": {("destruction_list", "zaak")},
161+
},
162+
),
163+
]

backend/src/openarchiefbeheer/destruction/migrations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)