Skip to content

Commit edc2bbe

Browse files
committed
Added permissions endpoint
1 parent 312d006 commit edc2bbe

File tree

10 files changed

+435
-3
lines changed

10 files changed

+435
-3
lines changed

api/serializers.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from abc import abstractmethod
12
from datetime import datetime
2-
from typing import Literal, Optional
3+
from typing import Literal, Optional, Union
34
from uuid import UUID
45

56
from django.contrib.auth import get_user_model
@@ -11,6 +12,7 @@
1112
from rest_framework import serializers
1213

1314
from drf_extra_fields.geo_fields import PointField
15+
from rest_framework_dataclasses.serializers import DataclassSerializer
1416
from taggit.serializers import TaggitSerializer
1517

1618
from tigacrafting.models import (
@@ -21,6 +23,7 @@
2123
PhotoPrediction,
2224
FavoritedReports
2325
)
26+
from tigacrafting.permissions import Permissions
2427
from tigaserver_app.models import (
2528
NotificationContent,
2629
Notification,
@@ -102,6 +105,82 @@ class Meta:
102105
"received_at": {"source": "server_upload_time"},
103106
}
104107

108+
class PermissionsSerializer(DataclassSerializer):
109+
class Meta:
110+
dataclass = Permissions
111+
112+
class BaseRolePermissionSerializer(serializers.Serializer):
113+
role = serializers.SerializerMethodField()
114+
permissions = serializers.SerializerMethodField()
115+
116+
@abstractmethod
117+
def _get_role(self, obj: User):
118+
raise NotImplementedError()
119+
120+
def get_role(self, obj: Union[User, TigaUser]) -> Literal['base', 'annotator', 'supervisor', 'reviewer', 'admin']:
121+
if isinstance(obj, User):
122+
return self._get_role(obj=obj)
123+
return 'base'
124+
125+
@extend_schema_field(PermissionsSerializer)
126+
def get_permissions(self, obj: Union[User, TigaUser]):
127+
permissions = []
128+
if isinstance(obj, User):
129+
permissions = UserStat.get_permissions(
130+
role=self.get_role(obj=obj)
131+
)
132+
elif isinstance(obj, TigaUser):
133+
permissions = TigaUser.get_permissions()
134+
return PermissionsSerializer(permissions).data
135+
136+
class UserPermissionSerializer(serializers.Serializer):
137+
class GeneralPermissionSerializer(BaseRolePermissionSerializer):
138+
is_staff = serializers.BooleanField()
139+
140+
def _get_role(self, obj: User):
141+
userstat = UserStat.objects.filter(user=obj).first()
142+
if userstat:
143+
return userstat.get_role()
144+
return 'base'
145+
146+
class CountryPermissionSerializer(BaseRolePermissionSerializer):
147+
def __init__(self, *args, **kwargs):
148+
self.country = kwargs.pop('country', None)
149+
super().__init__(*args, **kwargs)
150+
151+
country = serializers.SerializerMethodField()
152+
153+
def _get_role(self, obj: User):
154+
userstat = UserStat.objects.filter(user=obj).first()
155+
if userstat:
156+
return userstat.get_role(country=self.country)
157+
return 'base'
158+
159+
@extend_schema_field(CountrySerializer)
160+
def get_country(self, obj: Union[User, TigaUser]):
161+
return CountrySerializer(self.country).data
162+
163+
general = serializers.SerializerMethodField()
164+
countries = serializers.SerializerMethodField()
165+
166+
@extend_schema_field(GeneralPermissionSerializer)
167+
def get_general(self, obj: Union[User, TigaUser]):
168+
return self.GeneralPermissionSerializer(obj).data
169+
170+
@extend_schema_field(CountryPermissionSerializer(many=True))
171+
def get_countries(self, obj: Union[User, TigaUser]):
172+
if not isinstance(obj, User):
173+
return self.CountryPermissionSerializer(many=True).data
174+
userstat = UserStat.objects.filter(user=obj).first()
175+
if userstat:
176+
result = []
177+
for country in [userstat.national_supervisor_of, userstat.native_of]:
178+
if not country:
179+
continue
180+
result.append(self.CountryPermissionSerializer(instance=obj, country=country).data)
181+
return result
182+
return self.CountryPermissionSerializer(many=True).data
183+
105184
class UserSerializer(serializers.ModelSerializer):
106185
class UserScoreSerializer(serializers.ModelSerializer):
107186
value = serializers.IntegerField(source="score_v2", min_value=0, read_only=True)
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
3+
test_name: Anonymous user can not get permissions
4+
5+
includes:
6+
- !include schema.yml
7+
8+
marks:
9+
- usefixtures:
10+
- api_live_url
11+
12+
stages:
13+
- name: Anonymous user can not get permissions
14+
request:
15+
url: "{api_live_url}/me/permissions/"
16+
method: GET
17+
response:
18+
status_code: 401
19+
20+
---
21+
22+
test_name: Mobile users get permissions
23+
24+
includes:
25+
- !include schema.yml
26+
27+
marks:
28+
- usefixtures:
29+
- api_live_url
30+
- app_user
31+
- app_user_token
32+
33+
stages:
34+
- name: Mobile users get permissions.
35+
request:
36+
url: "{api_live_url}/me/permissions/"
37+
method: GET
38+
headers:
39+
Authorization: 'Bearer {app_user_token}'
40+
response:
41+
status_code: 200
42+
json:
43+
general:
44+
role: "base"
45+
permissions:
46+
annotation:
47+
add: false
48+
change: false
49+
view: false
50+
delete: false
51+
mark_as_decisive: false
52+
identification_task:
53+
add: false
54+
change: false
55+
view: false
56+
delete: false
57+
review:
58+
add: false
59+
change: false
60+
view: false
61+
delete: false
62+
is_staff: false
63+
countries: []
64+
65+
---
66+
67+
test_name: Regular users get permissions
68+
69+
includes:
70+
- !include schema.yml
71+
72+
marks:
73+
- usefixtures:
74+
- api_live_url
75+
- user
76+
- jwt_token_user
77+
78+
stages:
79+
- name: Regular users get permissions.
80+
request:
81+
url: "{api_live_url}/me/permissions/"
82+
method: GET
83+
headers:
84+
Authorization: 'Bearer {jwt_token_user}'
85+
response:
86+
status_code: 200
87+
json:
88+
general:
89+
role: "base"
90+
permissions:
91+
annotation:
92+
add: false
93+
change: false
94+
view: false
95+
delete: false
96+
mark_as_decisive: false
97+
identification_task:
98+
add: false
99+
change: false
100+
view: false
101+
delete: false
102+
review:
103+
add: false
104+
change: false
105+
view: false
106+
delete: false
107+
is_staff: false
108+
countries: []
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
3+
name: Common test information
4+
description: Login information for test server
5+
6+
variables:
7+
endpoint: "permissions"
8+
response_data_validation:
9+
general:
10+
role: !anystr
11+
permissions: &retrieve_validation
12+
annotation:
13+
add: !anybool
14+
change: !anybool
15+
view: !anybool
16+
delete: !anybool
17+
mark_as_decisive: !anybool
18+
identification_task:
19+
add: !anybool
20+
change: !anybool
21+
view: !anybool
22+
delete: !anybool
23+
review:
24+
add: !anybool
25+
change: !anybool
26+
view: !anybool
27+
delete: !anybool
28+
is_staff: !anybool
29+
countries:
30+
country:
31+
id: !anyint
32+
name_en: !anystr
33+
iso3_code: !anystr
34+
role: !anystr
35+
permissions:
36+
<<: *retrieve_validation

api/tests/test_views.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pytest
77

88
from django.contrib.auth import get_user_model
9+
from django.contrib.auth.models import Group
910
from django.utils import timezone
1011
from django.utils.module_loading import import_string
1112

@@ -973,4 +974,119 @@ def test_classification_null_shoud_set_status_to_hidden(self, api_client, endpoi
973974

974975
annotation = ExpertReportAnnotation.objects.get(pk=response.data['id'])
975976
assert annotation.status == ExpertReportAnnotation.STATUS_HIDDEN
976-
assert annotation.taxon is None
977+
assert annotation.taxon is None
978+
979+
@pytest.mark.django_db
980+
class TestPermissionsApi:
981+
@pytest.fixture
982+
def me_endpoint(self):
983+
return f'/api/v1/me/permissions/'
984+
985+
@pytest.fixture
986+
def api_client(self, user):
987+
api_client = APIClient()
988+
api_client.force_login(user=user)
989+
990+
return api_client
991+
992+
def test_general_role_base(self, api_client, me_endpoint):
993+
response = api_client.get(
994+
me_endpoint,
995+
format='json'
996+
)
997+
assert response.status_code == status.HTTP_200_OK
998+
assert response.data['general']['role'] == 'base'
999+
1000+
def test_general_role_annotator(self, api_client, user, me_endpoint):
1001+
expert_group, _ = Group.objects.get_or_create(name='expert')
1002+
user.groups.add(expert_group)
1003+
1004+
response = api_client.get(
1005+
me_endpoint,
1006+
format='json'
1007+
)
1008+
assert response.status_code == status.HTTP_200_OK
1009+
assert response.data['general']['role'] == 'annotator'
1010+
assert not response.data['general']['permissions']['review']['add']
1011+
assert not response.data['general']['permissions']['review']['view']
1012+
1013+
def test_general_role_reviewer(self, api_client, user, me_endpoint):
1014+
superexpert_group, _ = Group.objects.get_or_create(name='superexpert')
1015+
user.groups.add(superexpert_group)
1016+
1017+
response = api_client.get(
1018+
me_endpoint,
1019+
format='json'
1020+
)
1021+
assert response.status_code == status.HTTP_200_OK
1022+
assert response.data['general']['role'] == 'reviewer'
1023+
assert response.data['general']['permissions']['review']['add']
1024+
assert response.data['general']['permissions']['review']['view']
1025+
1026+
def test_general_role_admin(self, api_client, user, me_endpoint):
1027+
user.is_superuser = True
1028+
user.save()
1029+
1030+
response = api_client.get(
1031+
me_endpoint,
1032+
format='json'
1033+
)
1034+
assert response.status_code == status.HTTP_200_OK
1035+
assert response.data['general']['role'] == 'admin'
1036+
1037+
@pytest.mark.parametrize(
1038+
"is_staff",
1039+
[True, False]
1040+
)
1041+
def test_general_is_staff(self, user, api_client, me_endpoint, is_staff):
1042+
user.is_staff = is_staff
1043+
user.save()
1044+
1045+
response = api_client.get(
1046+
me_endpoint,
1047+
format='json'
1048+
)
1049+
assert response.status_code == status.HTTP_200_OK
1050+
assert response.data['general']['is_staff'] == is_staff
1051+
1052+
def test_countries_role_supervisor(self, api_client, user, me_endpoint, country):
1053+
expert_group, _ = Group.objects.get_or_create(name='expert')
1054+
user.groups.add(expert_group)
1055+
1056+
userstat = user.userstat
1057+
userstat.national_supervisor_of = country
1058+
userstat.save()
1059+
1060+
response = api_client.get(
1061+
me_endpoint,
1062+
format='json'
1063+
)
1064+
assert response.status_code == status.HTTP_200_OK
1065+
assert response.data['general']['role'] == 'annotator'
1066+
assert response.data['countries'][0]['country']['id'] == country.pk
1067+
assert response.data['countries'][0]['role'] == 'supervisor'
1068+
assert response.data['countries'][0]['permissions']['annotation']['mark_as_decisive']
1069+
assert response.data['countries'][0]['permissions']['identification_task']['view']
1070+
assert not response.data['countries'][0]['permissions']['review']['add']
1071+
assert not response.data['countries'][0]['permissions']['review']['view']
1072+
1073+
def test_countries_role_annotator(self, api_client, user, me_endpoint, country):
1074+
expert_group, _ = Group.objects.get_or_create(name='expert')
1075+
user.groups.add(expert_group)
1076+
1077+
userstat = user.userstat
1078+
userstat.native_of = country
1079+
userstat.save()
1080+
1081+
response = api_client.get(
1082+
me_endpoint,
1083+
format='json'
1084+
)
1085+
assert response.status_code == status.HTTP_200_OK
1086+
assert response.data['general']['role'] == 'annotator'
1087+
assert response.data['countries'][0]['role'] == 'annotator'
1088+
assert response.data['countries'][0]['country']['id'] == country.pk
1089+
assert not response.data['countries'][0]['permissions']['annotation']['mark_as_decisive']
1090+
assert not response.data['countries'][0]['permissions']['identification_task']['view']
1091+
assert not response.data['countries'][0]['permissions']['review']['add']
1092+
assert not response.data['countries'][0]['permissions']['review']['view']

0 commit comments

Comments
 (0)