Skip to content

Commit b5a6bba

Browse files
committed
Added authorization for boundaries.
1 parent 1441e2a commit b5a6bba

File tree

15 files changed

+366
-1
lines changed

15 files changed

+366
-1
lines changed

config/settings/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102

103103
LOCAL_APPS = [
104104
"mosquito_alert.annotations",
105+
"mosquito_alert.authorization",
105106
"mosquito_alert.bites",
106107
"mosquito_alert.breeding_sites",
107108
"mosquito_alert.epidemiology",

mosquito_alert/authorization/__init__.py

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.contrib import admin
2+
3+
from .models import BoundaryAuthorization, BoundaryMembership
4+
5+
6+
class BoundaryMembershipInline(admin.StackedInline):
7+
model = BoundaryMembership
8+
extra = 1
9+
fields = ("user", "role")
10+
11+
12+
@admin.register(BoundaryAuthorization)
13+
class BoundaryAuthorizationAdmin(admin.ModelAdmin):
14+
list_display = ("boundary", "supervisor_exclusivity_days", "members_only")
15+
search_fields = ("boundary__name",)
16+
17+
fields = ("boundary", "supervisor_exclusivity_days", "members_only")
18+
inlines = [BoundaryMembershipInline]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class AuthorizationConfig(AppConfig):
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "mosquito_alert.authorization"
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Generated by Django 4.2.3 on 2024-10-18 14:52
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
import django.db.models.functions.datetime
7+
import django.utils.timezone
8+
9+
10+
class Migration(migrations.Migration):
11+
initial = True
12+
13+
dependencies = [
14+
("geo", "0003_location_timezone"),
15+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16+
]
17+
18+
operations = [
19+
migrations.CreateModel(
20+
name="BoundaryAuthorization",
21+
fields=[
22+
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
23+
("created_at", models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False)),
24+
("updated_at", models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False)),
25+
("supervisor_exclusivity_days", models.PositiveSmallIntegerField(default=15)),
26+
("members_only", models.BooleanField(default=False)),
27+
(
28+
"boundary",
29+
models.OneToOneField(
30+
on_delete=django.db.models.deletion.CASCADE, related_name="authorization", to="geo.boundary"
31+
),
32+
),
33+
],
34+
options={
35+
"verbose_name": "Boundary Authorization",
36+
"verbose_name_plural": "Boundary Authorizations",
37+
"abstract": False,
38+
},
39+
),
40+
migrations.CreateModel(
41+
name="BoundaryMembership",
42+
fields=[
43+
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
44+
("created_at", models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False)),
45+
("updated_at", models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False)),
46+
(
47+
"role",
48+
models.CharField(
49+
choices=[("s", "Supervisor"), ("i", "Identificator"), ("v", "Viewer")],
50+
db_index=True,
51+
max_length=1,
52+
),
53+
),
54+
(
55+
"boundary_authorization",
56+
models.ForeignKey(
57+
on_delete=django.db.models.deletion.CASCADE,
58+
related_name="memberships",
59+
to="authorization.boundaryauthorization",
60+
),
61+
),
62+
(
63+
"user",
64+
models.ForeignKey(
65+
on_delete=django.db.models.deletion.CASCADE,
66+
related_name="boundary_memberships",
67+
to=settings.AUTH_USER_MODEL,
68+
),
69+
),
70+
],
71+
options={
72+
"abstract": False,
73+
},
74+
),
75+
migrations.AddField(
76+
model_name="boundaryauthorization",
77+
name="users",
78+
field=models.ManyToManyField(through="authorization.BoundaryMembership", to=settings.AUTH_USER_MODEL),
79+
),
80+
migrations.AddConstraint(
81+
model_name="boundarymembership",
82+
constraint=models.CheckConstraint(
83+
check=models.Q(("created_at__lte", django.db.models.functions.datetime.Now())),
84+
name="authorization_boundarymembership_created_at_cannot_be_future_dated",
85+
),
86+
),
87+
migrations.AddConstraint(
88+
model_name="boundarymembership",
89+
constraint=models.CheckConstraint(
90+
check=models.Q(("updated_at__lte", django.db.models.functions.datetime.Now())),
91+
name="authorization_boundarymembership_updated_at_cannot_be_future_dated",
92+
),
93+
),
94+
migrations.AddConstraint(
95+
model_name="boundarymembership",
96+
constraint=models.CheckConstraint(
97+
check=models.Q(("updated_at__gte", models.F("created_at"))),
98+
name="authorization_boundarymembership_updated_at_must_be_after_created_at",
99+
),
100+
),
101+
migrations.AddConstraint(
102+
model_name="boundaryauthorization",
103+
constraint=models.CheckConstraint(
104+
check=models.Q(("created_at__lte", django.db.models.functions.datetime.Now())),
105+
name="authorization_boundaryauthorization_created_at_cannot_be_future_dated",
106+
),
107+
),
108+
migrations.AddConstraint(
109+
model_name="boundaryauthorization",
110+
constraint=models.CheckConstraint(
111+
check=models.Q(("updated_at__lte", django.db.models.functions.datetime.Now())),
112+
name="authorization_boundaryauthorization_updated_at_cannot_be_future_dated",
113+
),
114+
),
115+
migrations.AddConstraint(
116+
model_name="boundaryauthorization",
117+
constraint=models.CheckConstraint(
118+
check=models.Q(("updated_at__gte", models.F("created_at"))),
119+
name="authorization_boundaryauthorization_updated_at_must_be_after_created_at",
120+
),
121+
),
122+
]

mosquito_alert/authorization/migrations/__init__.py

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from django.conf import settings
2+
from django.db import models
3+
from django.utils.translation import gettext_lazy as _
4+
5+
from mosquito_alert.geo.models import Boundary
6+
from mosquito_alert.utils.models import TimeStampedModel
7+
8+
9+
class BoundaryAuthorization(TimeStampedModel):
10+
# Relations
11+
boundary = models.OneToOneField(Boundary, on_delete=models.CASCADE, related_name="authorization")
12+
users = models.ManyToManyField(settings.AUTH_USER_MODEL, through="BoundaryMembership")
13+
14+
# Attributes - Mandatory
15+
supervisor_exclusivity_days = models.PositiveSmallIntegerField(default=15)
16+
members_only = models.BooleanField(default=False)
17+
18+
# Attributes - Optional
19+
20+
# Object Manager
21+
# Custom Properties
22+
# Methods
23+
# Meta and String
24+
class Meta(TimeStampedModel.Meta):
25+
verbose_name = _("Boundary Authorization")
26+
verbose_name_plural = _("Boundary Authorizations")
27+
28+
29+
class BoundaryMembership(TimeStampedModel):
30+
class RoleType(models.TextChoices):
31+
SUPERVISOR = "s", _("Supervisor")
32+
IDENTIFICATOR = "i", _("Identificator")
33+
VIEWER = "v", _("Viewer")
34+
35+
# Relations
36+
boundary_authorization = models.ForeignKey(
37+
BoundaryAuthorization, on_delete=models.CASCADE, related_name="memberships"
38+
)
39+
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="boundary_memberships")
40+
41+
# Attributes - Mandatory
42+
role = models.CharField(max_length=1, choices=RoleType.choices, db_index=True)
43+
44+
# Attributes - Optional
45+
46+
# Object Manager
47+
# Custom Properties
48+
# Methods
49+
# Meta and String
50+
class Meta(TimeStampedModel.Meta):
51+
pass

mosquito_alert/authorization/tests/__init__.py

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import factory
2+
from factory.django import DjangoModelFactory
3+
4+
from mosquito_alert.geo.tests.factories import BoundaryFactory
5+
from mosquito_alert.users.tests.factories import UserFactory
6+
7+
from ..models import BoundaryAuthorization, BoundaryMembership
8+
9+
10+
class BoundaryAuthorizationFactory(DjangoModelFactory):
11+
boundary = factory.SubFactory(BoundaryFactory)
12+
13+
class Meta:
14+
model = BoundaryAuthorization
15+
16+
17+
class BoundaryMembershipFactory(factory.django.DjangoModelFactory):
18+
boundary_authorization = factory.SubFactory(BoundaryAuthorizationFactory)
19+
user = factory.SubFactory(UserFactory)
20+
role = factory.Faker("random_element", elements=BoundaryMembership.RoleType.values)
21+
22+
class Meta:
23+
model = BoundaryMembership
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from django.urls import reverse
2+
3+
from mosquito_alert.geo.tests.factories import BoundaryFactory
4+
from mosquito_alert.users.tests.factories import UserFactory
5+
6+
from ..models import BoundaryAuthorization, BoundaryMembership
7+
from .factories import BoundaryAuthorizationFactory
8+
9+
10+
class TestBoundaryAuthorizationAdmin:
11+
def test_changelist(self, admin_client):
12+
url = reverse("admin:authorization_boundaryauthorization_changelist")
13+
response = admin_client.get(url)
14+
assert response.status_code == 200
15+
16+
def test_add(self, admin_client, freezer):
17+
url = reverse("admin:authorization_boundaryauthorization_add")
18+
response = admin_client.get(url)
19+
assert response.status_code == 200
20+
21+
boundary = BoundaryFactory()
22+
user = UserFactory()
23+
24+
response = admin_client.post(
25+
url,
26+
data={
27+
"boundary": boundary.pk,
28+
"supervisor_exclusivity_days": "15",
29+
"memberships-TOTAL_FORMS": "1",
30+
"memberships-INITIAL_FORMS": "0",
31+
"memberships-0-user": user.pk,
32+
"memberships-0-role": BoundaryMembership.RoleType.SUPERVISOR.value,
33+
},
34+
)
35+
assert response.status_code == 302
36+
assert BoundaryAuthorization.objects.filter(boundary=boundary).exists()
37+
38+
def test_view(self, admin_client):
39+
boundary_auth = BoundaryAuthorizationFactory()
40+
url = reverse("admin:authorization_boundaryauthorization_change", kwargs={"object_id": boundary_auth.pk})
41+
response = admin_client.get(url)
42+
assert response.status_code == 200

0 commit comments

Comments
 (0)