Skip to content

Commit 0296399

Browse files
authored
fix: UTC-22: Validation error when updating task data via API (#7644)
Co-authored-by: mcanu <[email protected]>
1 parent c5e5295 commit 0296399

File tree

3 files changed

+99
-128
lines changed

3 files changed

+99
-128
lines changed

label_studio/tasks/serializers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ def project(self, task=None):
176176

177177
def validate(self, task):
178178
instance = self.instance if hasattr(self, 'instance') else None
179-
validator = TaskValidator(self.project(), instance)
179+
validator = TaskValidator(
180+
self.project(task=instance),
181+
instance=instance if 'data' not in task else None,
182+
)
180183
return validator.validate(task)
181184

182185
def to_representation(self, instance):

label_studio/tasks/tests/test_api.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from organizations.tests.factories import OrganizationFactory
2+
from projects.tests.factories import ProjectFactory
3+
from rest_framework.test import APITestCase
4+
from tasks.tests.factories import TaskFactory
5+
6+
7+
class TestTaskAPI(APITestCase):
8+
@classmethod
9+
def setUpTestData(cls):
10+
cls.organization = OrganizationFactory()
11+
cls.project = ProjectFactory(organization=cls.organization)
12+
cls.user = cls.organization.created_by
13+
14+
def test_get_task(self):
15+
task = TaskFactory(project=self.project, data={'text': 'test'})
16+
17+
self.client.force_authenticate(user=self.user)
18+
response = self.client.get(f'/api/tasks/{task.id}/')
19+
assert response.status_code == 200
20+
21+
assert response.json() == {
22+
'id': task.id,
23+
'project': self.project.id,
24+
'created_at': task.created_at.isoformat().replace('+00:00', 'Z'),
25+
'updated_at': task.updated_at.isoformat().replace('+00:00', 'Z'),
26+
'annotations': [],
27+
'predictions': [],
28+
'drafts': [],
29+
'data': {'text': 'test'},
30+
'meta': {},
31+
'updated_by': [],
32+
'is_labeled': False,
33+
'overlap': 1,
34+
'file_upload': None,
35+
'annotations_ids': '',
36+
'annotations_results': '',
37+
'annotators': [],
38+
'completed_at': None,
39+
'predictions_model_versions': '',
40+
'draft_exists': False,
41+
'predictions_results': '',
42+
'predictions_score': None,
43+
'total_annotations': 0,
44+
'total_predictions': 0,
45+
'avg_lead_time': None,
46+
'cancelled_annotations': 0,
47+
'inner_id': task.inner_id,
48+
'storage_filename': None,
49+
'comment_authors': [],
50+
'comment_count': 0,
51+
'last_comment_updated_at': None,
52+
'unresolved_comment_count': 0,
53+
}
54+
55+
def test_patch_task(self):
56+
task = TaskFactory(project=self.project, data={'text': 'test'})
57+
58+
payload = {
59+
'annotations': [],
60+
'predictions': [],
61+
'data': {'text': 'changed test'},
62+
'meta': {},
63+
'created_at': '',
64+
'updated_at': '',
65+
'updated_by': None,
66+
'is_labeled': False,
67+
'file_upload': None,
68+
}
69+
70+
self.client.force_authenticate(user=self.user)
71+
response = self.client.patch(f'/api/tasks/{task.id}/', data=payload, format='json')
72+
assert response.status_code == 200
73+
task.refresh_from_db()
74+
assert response.json() == {
75+
'id': task.id,
76+
'project': self.project.id,
77+
'created_at': task.created_at.isoformat().replace('+00:00', 'Z'),
78+
'updated_at': task.updated_at.isoformat().replace('+00:00', 'Z'),
79+
'annotations': [],
80+
'predictions': [],
81+
'data': {'text': 'changed test'},
82+
'meta': {},
83+
'updated_by': None,
84+
'is_labeled': False,
85+
'overlap': 1,
86+
'file_upload': None,
87+
'total_annotations': 0,
88+
'total_predictions': 0,
89+
'cancelled_annotations': 0,
90+
'inner_id': task.inner_id,
91+
'comment_authors': [],
92+
'comment_count': 0,
93+
'last_comment_updated_at': None,
94+
'unresolved_comment_count': 0,
95+
}

label_studio/tests/test_api.py

Lines changed: 0 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -271,130 +271,3 @@ def test_creating_activating_new_ml_backend(
271271
def test_delete_annotations(business_client, configured_project):
272272
business_client.delete(f'/api/projects/{configured_project.id}/annotations/')
273273
assert not Annotation.objects.filter(task__project=configured_project.id).exists()
274-
275-
276-
# --- TaskAPI ---
277-
278-
279-
@pytest.mark.parametrize(
280-
'response, status_code',
281-
[
282-
# status OK
283-
(
284-
{
285-
'annotations': [],
286-
'predictions': [],
287-
'drafts': [],
288-
'data': {'text': 'text B', 'meta_info': 'meta info B'},
289-
'meta': {},
290-
'created_at': '',
291-
'updated_at': '',
292-
'updated_by': [],
293-
'is_labeled': False,
294-
'project': 0,
295-
'overlap': 1,
296-
'file_upload': None,
297-
'annotations_ids': '',
298-
'annotations_results': '',
299-
'annotators': [],
300-
'completed_at': None,
301-
'predictions_model_versions': '',
302-
'draft_exists': False,
303-
'predictions_results': '',
304-
'predictions_score': None,
305-
'total_annotations': 0,
306-
'total_predictions': 0,
307-
'avg_lead_time': None,
308-
'cancelled_annotations': 0,
309-
'inner_id': 0,
310-
'storage_filename': None,
311-
'comment_authors': [],
312-
'comment_count': 0,
313-
'last_comment_updated_at': None,
314-
'unresolved_comment_count': 0,
315-
},
316-
200,
317-
)
318-
],
319-
)
320-
@pytest.mark.django_db
321-
def test_get_task(client_and_token, configured_project, response, status_code):
322-
client, token = client_and_token
323-
task = configured_project.tasks.order_by('-id').all()[0]
324-
response['project'] = configured_project.id
325-
response['created_at'] = task.created_at.isoformat().replace('+00:00', 'Z')
326-
response['updated_at'] = task.updated_at.isoformat().replace('+00:00', 'Z')
327-
response['id'] = task.id
328-
r = client.get(
329-
f'/api/tasks/{task.id}/', content_type='application/json', headers={'Authorization': f'Token {token}'}
330-
)
331-
assert r.status_code == status_code
332-
if response:
333-
assert r.json() == response
334-
335-
336-
@pytest.mark.parametrize(
337-
'payload, response, status_code',
338-
[
339-
# status OK
340-
(
341-
{
342-
'annotations': [],
343-
'predictions': [],
344-
'data': {'text': 'TEST1', 'meta_info': 'TEST2'},
345-
'meta': {},
346-
'created_at': '',
347-
'updated_at': '',
348-
'updated_by': None,
349-
'is_labeled': False,
350-
'project': 0,
351-
'file_upload': None,
352-
},
353-
{
354-
'id': 0,
355-
'annotations': [],
356-
'predictions': [],
357-
'data': {'text': 'TEST1', 'meta_info': 'TEST2'},
358-
'meta': {},
359-
'created_at': '',
360-
'updated_at': '',
361-
'updated_by': None,
362-
'is_labeled': False,
363-
'project': 0,
364-
'overlap': 1,
365-
'file_upload': None,
366-
'inner_id': 1,
367-
'comment_authors': [],
368-
'comment_count': 0,
369-
'last_comment_updated_at': None,
370-
'unresolved_comment_count': 0,
371-
},
372-
200,
373-
)
374-
],
375-
)
376-
@pytest.mark.django_db
377-
def test_patch_task(client_and_token, configured_project, payload, response, status_code):
378-
client, token = client_and_token
379-
task = configured_project.tasks.order_by('-updated_at').all()[0]
380-
payload['project'] = configured_project.id
381-
382-
r = client.patch(
383-
f'/api/tasks/{task.id}/',
384-
data=json.dumps(payload),
385-
content_type='application/json',
386-
headers={'Authorization': f'Token {token}'},
387-
)
388-
389-
task = configured_project.tasks.order_by('-updated_at').all()[0] # call DB again after update
390-
response['project'] = configured_project.id
391-
response['created_at'] = task.created_at.isoformat().replace('+00:00', 'Z')
392-
response['updated_at'] = task.updated_at.isoformat().replace('+00:00', 'Z')
393-
response['id'] = task.id
394-
response['total_annotations'] = 0
395-
response['cancelled_annotations'] = 0
396-
response['total_predictions'] = 0
397-
398-
assert r.status_code == status_code
399-
if response:
400-
assert r.json() == response

0 commit comments

Comments
 (0)