Skip to content

Commit 7e6a477

Browse files
committed
Add POST identification task review
1 parent c141d50 commit 7e6a477

File tree

9 files changed

+388
-9
lines changed

9 files changed

+388
-9
lines changed

api/permissions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from rest_framework import permissions
55

66
from tigacrafting.models import IdentificationTask, ExpertReportAnnotation, UserStat
7+
from tigacrafting.permissions import ReviewPermission
78
from tigaserver_app.models import TigaUser, Notification
89

910
from .utils import get_fk_fieldnames
@@ -252,6 +253,17 @@ def has_permission(self, request, view):
252253
obj_or_klass=ExpertReportAnnotation
253254
)
254255

256+
class IdentificationTaskReviewPermissions(IsRegularUser):
257+
def has_permission(self, request, view):
258+
if not super().has_permission(request, view):
259+
return False
260+
261+
return UserRolePermission().check_permissions(
262+
user=request.user,
263+
action='add',
264+
obj_or_klass=ReviewPermission
265+
)
266+
255267
class BaseIdentificationTaskAttributePermissions(BaseIdentificationTaskPermissions):
256268
pass
257269

api/serializers.py

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,7 @@ class Meta(BaseAssignmentSerializer.Meta):
11501150

11511151
class IdentificationTaskSerializer(serializers.ModelSerializer):
11521152
class IdentificationTaskReviewSerializer(serializers.ModelSerializer):
1153-
type = serializers.ChoiceField(source='review_type',choices=IdentificationTask.Review.choices)
1153+
action = serializers.ChoiceField(source='review_type',choices=IdentificationTask.Review.choices)
11541154

11551155
def to_representation(self, instance):
11561156
if self.allow_null and instance.review_type is None:
@@ -1160,7 +1160,7 @@ def to_representation(self, instance):
11601160
class Meta:
11611161
model = IdentificationTask
11621162
fields = (
1163-
"type",
1163+
"action",
11641164
"created_at"
11651165
)
11661166
extra_kwargs = {
@@ -1172,7 +1172,7 @@ class IdentificationTaskResultSerializer(serializers.ModelSerializer):
11721172
confidence = serializers.FloatField(min_value=0, max_value=1, read_only=True)
11731173
confidence_label = serializers.SerializerMethodField()
11741174
is_high_confidence = serializers.SerializerMethodField()
1175-
source = serializers.ChoiceField(source='result_source',choices=IdentificationTask.ResultSource.choices)
1175+
source = serializers.ChoiceField(source='result_source',read_only=True, choices=IdentificationTask.ResultSource.choices)
11761176

11771177
def get_confidence_label(self, obj) -> str:
11781178
return obj.confidence_label
@@ -1213,7 +1213,8 @@ class Meta(BaseAssignmentSerializer.Meta):
12131213
fields = ("user", "annotation_id",) + BaseAssignmentSerializer.Meta.fields
12141214

12151215
observation = SimplifiedObservationWithPhotosSerializer(source='report', read_only=True)
1216-
public_photo = SimplePhotoSerializer(source='photo', required=True)
1216+
public_photo_uuid = serializers.UUIDField(source='photo__uuid', write_only=True)
1217+
public_photo = SimplePhotoSerializer(source='photo', read_only=True)
12171218
review = IdentificationTaskReviewSerializer(source='*', allow_null=True, read_only=True)
12181219
result = IdentificationTaskResultSerializer(source='*', read_only=True, allow_null=True)
12191220
assignments = UserAssignmentSerializer(many=True, read_only=True)
@@ -1222,6 +1223,7 @@ class Meta:
12221223
model = IdentificationTask
12231224
fields = (
12241225
'observation',
1226+
'public_photo_uuid',
12251227
'public_photo',
12261228
'assignments',
12271229
'status',
@@ -1235,13 +1237,98 @@ class Meta:
12351237
'updated_at'
12361238
)
12371239
extra_kwargs = {
1238-
'status': {'default': IdentificationTask.Status.OPEN},
1240+
'status': {'default': IdentificationTask.Status.OPEN, 'read_only': True},
12391241
'public_note': {'allow_null': True, 'allow_blank': True},
12401242
'num_annotations': {'source': 'total_finished_annotations','min_value': 0},
12411243
'created_at': {'read_only': True},
12421244
'updated_at': {'read_only': True},
12431245
}
12441246

1247+
class CreateReviewSerializer(serializers.ModelSerializer):
1248+
action = serializers.HiddenField(default=None)
1249+
1250+
def validate(self, data):
1251+
del data['review_type']
1252+
data['validation_complete'] = True
1253+
data['simplified_annotation'] = True
1254+
data['report'] = self.context.get('report')
1255+
1256+
return data
1257+
1258+
def create(self, validated_data):
1259+
report = validated_data.pop('report')
1260+
1261+
# TODO: Create a Review model for this.
1262+
ExpertReportAnnotation.objects.update_or_create(
1263+
user=self.context.get('request').user,
1264+
report=report,
1265+
defaults=validated_data
1266+
)
1267+
1268+
identification_task = report.identification_task
1269+
identification_task.refresh_from_db()
1270+
return identification_task
1271+
1272+
class Meta:
1273+
model = IdentificationTask
1274+
fields = (
1275+
'action',
1276+
)
1277+
extra_kwargs = {
1278+
'action': {'source': 'review_type','read_only': False},
1279+
}
1280+
1281+
class CreateAgreeReviewSerializer(CreateReviewSerializer):
1282+
action = serializers.ChoiceField(source='review_type', choices=[IdentificationTask.Review.AGREE.value], default=IdentificationTask.Review.AGREE.value)
1283+
1284+
def validate(self, data):
1285+
data = super().validate(data)
1286+
1287+
data['revise'] = False
1288+
data['status'] = ExpertReportAnnotation.STATUS_HIDDEN if not data['report'].identification_task.is_safe else ExpertReportAnnotation.STATUS_PUBLIC
1289+
1290+
return data
1291+
1292+
class Meta(CreateReviewSerializer.Meta):
1293+
fields = (
1294+
'action',
1295+
)
1296+
1297+
class CreateOverwriteReviewSerializer(CreateReviewSerializer):
1298+
action = serializers.ChoiceField(source='review_type', choices=[IdentificationTask.Review.OVERWRITE.value], default=IdentificationTask.Review.OVERWRITE.value)
1299+
1300+
public_photo_uuid = serializers.UUIDField(source='photo__uuid', write_only=True)
1301+
result = AnnotationSerializer.AnnotationClassificationSerializer(source='*', required=True, allow_null=True)
1302+
1303+
def validate(self, data):
1304+
data = super().validate(data)
1305+
1306+
data['revise'] = True
1307+
data['status'] = ExpertReportAnnotation.STATUS_HIDDEN if not data.pop('is_safe') else ExpertReportAnnotation.STATUS_PUBLIC
1308+
data['simplified_annotation'] = False
1309+
data['edited_user_notes'] = data.pop('public_note', None)
1310+
1311+
if public_photo_uuid := data.pop('photo__uuid', None):
1312+
try:
1313+
data['best_photo'] = Photo.objects.get(report=data['report'], uuid=public_photo_uuid)
1314+
except Photo.DoesNotExist:
1315+
raise serializers.ValidationError("The photo does not does not exist or does not belong to the observation.")
1316+
1317+
return data
1318+
1319+
class Meta(CreateReviewSerializer.Meta):
1320+
fields = (
1321+
'action',
1322+
'public_photo_uuid',
1323+
'is_safe',
1324+
'public_note',
1325+
'result',
1326+
)
1327+
extra_kwargs = {
1328+
'is_safe': {'read_only': False},
1329+
'public_note': {'allow_null': True, 'allow_blank': False, 'read_only': False}
1330+
}
1331+
12451332
class ObservationSerializer(BaseReportWithPhotosSerializer):
12461333
class IdentificationSerializer(serializers.ModelSerializer):
12471334
photo = SimplePhotoSerializer(required=True)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import pytest
2+
3+
from api.tests.utils import grant_permission_to_user
4+
5+
from tigacrafting.models import IdentificationTask
6+
7+
# NOTE: needed for token with perms fixture
8+
@pytest.fixture
9+
def model_class():
10+
return IdentificationTask
11+
12+
@pytest.fixture
13+
def endpoint(identification_task):
14+
return f"identification-tasks/{identification_task.report.pk}/review"
15+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
3+
test_name: New identification tasks review can not be created by not regular users with permissions.
4+
5+
includes:
6+
- !include schema.yml
7+
8+
marks:
9+
- usefixtures:
10+
- api_live_url
11+
- endpoint
12+
- app_user_token
13+
- jwt_token_user
14+
15+
stages:
16+
- name: Review is not allowed for mobile users
17+
request:
18+
url: "{api_live_url}/{endpoint}/"
19+
headers:
20+
Authorization: "Bearer {app_user_token}"
21+
method: "POST"
22+
response:
23+
status_code: 403
24+
- name: Non auth user can not review new identification tasks.
25+
request:
26+
url: "{api_live_url}/{endpoint}/"
27+
method: "POST"
28+
response:
29+
status_code: 401
30+
- name: User without perm can not review new identification tasks.
31+
request:
32+
url: "{api_live_url}/{endpoint}/"
33+
method: "POST"
34+
headers:
35+
Authorization: "Bearer {jwt_token_user:s}"
36+
response:
37+
status_code: 403
38+
39+
---
40+
41+
test_name: Identification tasks reviews (agree) can be made only by authenticated users with permissions.
42+
43+
includes:
44+
- !include schema.yml
45+
46+
marks:
47+
- usefixtures:
48+
- api_live_url
49+
- endpoint
50+
- user_with_role_reviewer
51+
- jwt_token_user
52+
53+
stages:
54+
- name: User with perm can review identification tasks.
55+
request:
56+
url: "{api_live_url}/{endpoint}/"
57+
method: "POST"
58+
headers:
59+
Authorization: "Bearer {jwt_token_user:s}"
60+
json:
61+
action: 'agree'
62+
response:
63+
status_code: 201
64+
json:
65+
action: 'agree'
66+
created_at: !re_fullmatch \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}Z
67+
68+
69+
---
70+
71+
test_name: Identification tasks reviews (overwrite) can be made only by authenticated users with permissions.
72+
73+
includes:
74+
- !include schema.yml
75+
76+
marks:
77+
- usefixtures:
78+
- api_live_url
79+
- endpoint
80+
- user_with_role_reviewer
81+
- jwt_token_user
82+
83+
stages:
84+
- name: User with perm can review identification tasks.
85+
request:
86+
url: "{api_live_url}/{endpoint}/"
87+
method: "POST"
88+
headers:
89+
Authorization: "Bearer {jwt_token_user:s}"
90+
json:
91+
action: 'overwrite'
92+
public_photo_uuid: "{identification_task.photo.uuid}"
93+
is_safe: True
94+
public_note: "Test new public note"
95+
result: null
96+
response:
97+
status_code: 201
98+
json:
99+
action: 'overwrite'
100+
created_at: !re_fullmatch \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}Z
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
3+
name: Common test information
4+
description: Login information for test server
5+
6+
variables:
7+
response_data_validation: &retrieve_validation
8+
action: !anystr
9+
created_at: !re_fullmatch \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}Z
10+
response_list_data_validation: &response_list_validation
11+
count: !anyint
12+
next: !anything
13+
previous: !anything
14+
results: [
15+
<<: *retrieve_validation
16+
]

0 commit comments

Comments
 (0)