Skip to content

Commit abe7869

Browse files
authored
Merge pull request #4 from maykinmedia/feature/reviewers-endpoint
Endpoint to retrieve reviewers
2 parents 5706ed3 + c33a9fa commit abe7869

File tree

10 files changed

+226
-1
lines changed

10 files changed

+226
-1
lines changed

backend/src/openarchiefbeheer/accounts/admin.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
from django.contrib.auth.admin import UserAdmin as _UserAdmin
44
from django.core.exceptions import PermissionDenied, ValidationError
55
from django.urls import reverse_lazy
6+
from django.utils.translation import gettext_lazy as _
67

78
from .forms import PreventPrivilegeEscalationMixin, UserChangeForm
8-
from .models import User
9+
from .models import Role, User
910
from .utils import validate_max_user_permissions
1011

1112

1213
@admin.register(User)
1314
class UserAdmin(_UserAdmin):
1415
hijack_success_url = reverse_lazy("root")
1516
form = UserChangeForm
17+
list_display = _UserAdmin.list_display + ("role",)
1618

1719
def get_form(self, request, obj=None, **kwargs):
1820
ModelForm = super().get_form(request, obj, **kwargs)
@@ -32,3 +34,17 @@ def user_change_password(self, request, id, form_url=""):
3234
raise PermissionDenied from exc
3335

3436
return super().user_change_password(request, id, form_url)
37+
38+
def get_fieldsets(self, request, obj=None):
39+
fieldsets = super().get_fieldsets(request, obj)
40+
return tuple(fieldsets) + ((_("Role"), {"fields": ("role",)}),)
41+
42+
43+
@admin.register(Role)
44+
class RoleAdmin(admin.ModelAdmin):
45+
list_display = (
46+
"name",
47+
"can_start_destruction",
48+
"can_review_destruction",
49+
"can_view_case_details",
50+
)

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

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from rest_framework import serializers
2+
3+
from ..models import Role, User
4+
5+
6+
class RoleSerializer(serializers.ModelSerializer):
7+
class Meta:
8+
model = Role
9+
fields = (
10+
"name",
11+
"can_start_destruction",
12+
"can_review_destruction",
13+
"can_view_case_details",
14+
)
15+
16+
17+
class UserSerializer(serializers.ModelSerializer):
18+
role = RoleSerializer()
19+
20+
class Meta:
21+
model = User
22+
fields = ("username", "first_name", "last_name", "email", "role")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from django.db.models import QuerySet
2+
from django.utils.translation import gettext_lazy as _
3+
4+
from drf_spectacular.utils import extend_schema
5+
from rest_framework.generics import ListAPIView
6+
7+
from openarchiefbeheer.accounts.models import User
8+
9+
from .serializers import UserSerializer
10+
11+
12+
@extend_schema(
13+
summary=_("Reviewers list"),
14+
description=_(
15+
"List all the users that have the permission to review destruction lists."
16+
),
17+
responses={
18+
200: UserSerializer(many=True),
19+
},
20+
)
21+
class ReviewersView(ListAPIView):
22+
serializer_class = UserSerializer
23+
24+
def get_queryset(self) -> QuerySet[User]:
25+
return User.objects.reviewers()

backend/src/openarchiefbeheer/accounts/managers.py

+3
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ def create_superuser(self, username, email, password, **extra_fields):
3232
raise ValueError("Superuser must have is_superuser=True.")
3333

3434
return self._create_user(username, email, password, **extra_fields)
35+
36+
def reviewers(self):
37+
return self.select_related("role").filter(role__can_review_destruction=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Generated by Django 4.2.11 on 2024-04-30 12:51
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("accounts", "0001_initial"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="Role",
16+
fields=[
17+
(
18+
"id",
19+
models.AutoField(
20+
auto_created=True,
21+
primary_key=True,
22+
serialize=False,
23+
verbose_name="ID",
24+
),
25+
),
26+
(
27+
"name",
28+
models.CharField(
29+
help_text="Name of the role",
30+
max_length=255,
31+
unique=True,
32+
verbose_name="name",
33+
),
34+
),
35+
(
36+
"can_start_destruction",
37+
models.BooleanField(
38+
default=False,
39+
help_text="Indicates whether a user can create a list of cases to be deleted.",
40+
verbose_name="can start destruction",
41+
),
42+
),
43+
(
44+
"can_review_destruction",
45+
models.BooleanField(
46+
default=False,
47+
help_text="Indicates whether a user can review a list of cases to be deleted. They can approve it, reject it or provide feedback.",
48+
verbose_name="can review destruction",
49+
),
50+
),
51+
(
52+
"can_view_case_details",
53+
models.BooleanField(
54+
default=False,
55+
help_text="Indicates whether a user can view the contents of cases in a lists.",
56+
verbose_name="can view case details",
57+
),
58+
),
59+
],
60+
options={
61+
"verbose_name": "role",
62+
"verbose_name_plural": "roles",
63+
},
64+
),
65+
migrations.AddField(
66+
model_name="user",
67+
name="role",
68+
field=models.ForeignKey(
69+
blank=True,
70+
null=True,
71+
on_delete=django.db.models.deletion.SET_NULL,
72+
to="accounts.role",
73+
verbose_name="role",
74+
),
75+
),
76+
]

backend/src/openarchiefbeheer/accounts/models.py

+42
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ class User(AbstractBaseUser, PermissionsMixin):
4343
),
4444
)
4545
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
46+
role = models.ForeignKey(
47+
"accounts.Role",
48+
on_delete=models.SET_NULL,
49+
blank=True,
50+
null=True,
51+
verbose_name=_("role"),
52+
)
4653

4754
objects = UserManager()
4855

@@ -63,3 +70,38 @@ def get_full_name(self):
6370
def get_short_name(self):
6471
"Returns the short name for the user."
6572
return self.first_name
73+
74+
75+
class Role(models.Model):
76+
name = models.CharField(
77+
_("name"), max_length=255, unique=True, help_text=_("Name of the role")
78+
)
79+
can_start_destruction = models.BooleanField(
80+
_("can start destruction"),
81+
default=False,
82+
help_text=_(
83+
"Indicates whether a user can create a list of cases to be deleted."
84+
),
85+
)
86+
can_review_destruction = models.BooleanField(
87+
_("can review destruction"),
88+
default=False,
89+
help_text=_(
90+
"Indicates whether a user can review a list of cases to be deleted. "
91+
"They can approve it, reject it or provide feedback."
92+
),
93+
)
94+
can_view_case_details = models.BooleanField(
95+
_("can view case details"),
96+
default=False,
97+
help_text=_(
98+
"Indicates whether a user can view the contents of cases in a lists."
99+
),
100+
)
101+
102+
class Meta:
103+
verbose_name = _("role")
104+
verbose_name_plural = _("roles")
105+
106+
def __str__(self):
107+
return self.name

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

+10
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@
33
import factory
44
from factory.django import DjangoModelFactory
55

6+
from ..models import Role
7+
68
User = get_user_model()
79

810

11+
class RoleFactory(DjangoModelFactory):
12+
name = factory.Sequence(lambda n: f"Role {n}")
13+
14+
class Meta:
15+
model = Role
16+
17+
918
class UserFactory(DjangoModelFactory):
1019
username = factory.Sequence(lambda n: f"user-{n}")
1120
first_name = factory.Faker("first_name")
1221
last_name = factory.Faker("last_name")
1322
password = factory.PostGenerationMethodCall("set_password", "password")
23+
role = factory.SubFactory(RoleFactory)
1424

1525
class Meta:
1626
model = User
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from rest_framework import status
2+
from rest_framework.reverse import reverse
3+
from rest_framework.test import APITestCase
4+
5+
from openarchiefbeheer.accounts.tests.factories import UserFactory
6+
7+
8+
class RoleEndpointTests(APITestCase):
9+
def test_user_not_logged_in(self):
10+
response = self.client.get(reverse("api:reviewers"))
11+
12+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
13+
14+
def test_retrieve_reviewers(self):
15+
admin = UserFactory.create(is_superuser=True, role=None)
16+
UserFactory.create_batch(2, role__can_review_destruction=True)
17+
UserFactory.create_batch(2, role__can_review_destruction=False)
18+
19+
self.client.force_authenticate(user=admin)
20+
response = self.client.get(reverse("api:reviewers"))
21+
22+
self.assertEqual(response.status_code, status.HTTP_200_OK)
23+
24+
data = response.json()
25+
26+
self.assertEqual(len(data), 2)
27+
self.assertTrue(data[0]["role"]["canReviewDestruction"])
28+
self.assertTrue(data[1]["role"]["canReviewDestruction"])

backend/src/openarchiefbeheer/api/urls.py

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
SpectacularRedocView,
77
)
88

9+
from openarchiefbeheer.accounts.api.views import ReviewersView
10+
911
app_name = "api"
1012

1113
urlpatterns = [
@@ -35,4 +37,5 @@
3537
"openarchiefbeheer.api.authentication.urls", namespace="authentication"
3638
),
3739
),
40+
path("v1/reviewers/", ReviewersView.as_view(), name="reviewers"),
3841
]

0 commit comments

Comments
 (0)