Skip to content

Commit ed388db

Browse files
authored
Merge pull request doccano#527 from CatalystCode/bugfix/document-filter-collaborative-annotation
Bugfix/Fix collaborative annotation for stats and filter
2 parents 72cafaf + 290e2ca commit ed388db

File tree

3 files changed

+111
-26
lines changed

3 files changed

+111
-26
lines changed

app/api/filters.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class DocumentFilter(FilterSet):
1212
def filter_annotations(self, queryset, field_name, value):
1313
queryset = queryset.annotate(num_annotations=
1414
Count(field_name, filter=
15-
Q(**{ f"{field_name}__user": self.request.user})))
15+
Q(**{ f"{field_name}__user": self.request.user}) | Q(project__collaborative_annotation=True)))
1616

1717
should_have_annotations = not value
1818
if should_have_annotations:

app/api/tests/test_api.py

+104-21
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ def remove_all_role_mappings():
2929
RoleMapping.objects.all().delete()
3030

3131

32+
class TestUtilsMixin:
33+
def _patch_project(self, project, attribute, value):
34+
old_value = getattr(project, attribute, None)
35+
setattr(project, attribute, value)
36+
project.save()
37+
38+
def cleanup_project():
39+
setattr(project, attribute, old_value)
40+
project.save()
41+
42+
self.addCleanup(cleanup_project)
43+
44+
3245
@override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
3346
class TestProjectListAPI(APITestCase):
3447

@@ -363,7 +376,7 @@ def doCleanups(cls):
363376
remove_all_role_mappings()
364377

365378

366-
class TestDocumentListAPI(APITestCase):
379+
class TestDocumentListAPI(APITestCase, TestUtilsMixin):
367380

368381
@classmethod
369382
def setUpTestData(cls):
@@ -383,6 +396,8 @@ def setUpTestData(cls):
383396
384397

385398
cls.main_project = mommy.make('TextClassificationProject', users=[project_member, super_user])
399+
doc1 = mommy.make('Document', project=cls.main_project)
400+
doc2 = mommy.make('Document', project=cls.main_project)
386401
mommy.make('Document', project=cls.main_project)
387402

388403
cls.random_order_project = mommy.make('TextClassificationProject', users=[project_member, super_user],
@@ -399,11 +414,58 @@ def setUpTestData(cls):
399414
assign_user_to_role(project_member=project_member, project=cls.random_order_project,
400415
role_name=settings.ROLE_ANNOTATOR)
401416

402-
def test_returns_docs_to_project_member(self):
403-
self.client.login(username=self.project_member_name,
404-
password=self.project_member_pass)
405-
response = self.client.get(self.url, format='json')
417+
mommy.make('DocumentAnnotation', document=doc1, user=project_member)
418+
mommy.make('DocumentAnnotation', document=doc2, user=project_member)
419+
420+
def _test_list(self, url, username, password, expected_num_results):
421+
self.client.login(username=username, password=password)
422+
response = self.client.get(url, format='json')
406423
self.assertEqual(response.status_code, status.HTTP_200_OK)
424+
self.assertEqual(len(response.json().get('results')), expected_num_results)
425+
426+
def test_returns_docs_to_project_member(self):
427+
self._test_list(self.url,
428+
username=self.project_member_name,
429+
password=self.project_member_pass,
430+
expected_num_results=3)
431+
432+
def test_returns_docs_to_project_member_filtered_to_active(self):
433+
self._test_list('{}?doc_annotations__isnull=true'.format(self.url),
434+
username=self.project_member_name,
435+
password=self.project_member_pass,
436+
expected_num_results=1)
437+
438+
def test_returns_docs_to_project_member_filtered_to_completed(self):
439+
self._test_list('{}?doc_annotations__isnull=false'.format(self.url),
440+
username=self.project_member_name,
441+
password=self.project_member_pass,
442+
expected_num_results=2)
443+
444+
def test_returns_docs_to_project_member_filtered_to_active_with_collaborative_annotation(self):
445+
self._test_list('{}?doc_annotations__isnull=true'.format(self.url),
446+
username=self.super_user_name,
447+
password=self.super_user_pass,
448+
expected_num_results=3)
449+
450+
self._patch_project(self.main_project, 'collaborative_annotation', True)
451+
452+
self._test_list('{}?doc_annotations__isnull=true'.format(self.url),
453+
username=self.super_user_name,
454+
password=self.super_user_pass,
455+
expected_num_results=1)
456+
457+
def test_returns_docs_to_project_member_filtered_to_completed_with_collaborative_annotation(self):
458+
self._test_list('{}?doc_annotations__isnull=false'.format(self.url),
459+
username=self.super_user_name,
460+
password=self.super_user_pass,
461+
expected_num_results=0)
462+
463+
self._patch_project(self.main_project, 'collaborative_annotation', True)
464+
465+
self._test_list('{}?doc_annotations__isnull=false'.format(self.url),
466+
username=self.super_user_name,
467+
password=self.super_user_pass,
468+
expected_num_results=2)
407469

408470
def test_returns_docs_in_consistent_order_for_all_users(self):
409471
self.client.login(username=self.project_member_name, password=self.project_member_pass)
@@ -414,7 +476,7 @@ def test_returns_docs_in_consistent_order_for_all_users(self):
414476
user2_documents = self.client.get(self.url, format='json').json().get('results')
415477
self.client.logout()
416478

417-
self.assertEqual(user1_documents, user2_documents)
479+
self.assertEqual([doc['id'] for doc in user1_documents], [doc['id'] for doc in user2_documents])
418480

419481
def test_can_return_docs_in_consistent_random_order(self):
420482
self.client.login(username=self.project_member_name, password=self.project_member_pass)
@@ -439,10 +501,10 @@ def test_do_not_return_docs_to_non_project_member(self):
439501
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
440502

441503
def test_do_not_return_docs_of_other_projects(self):
442-
self.client.login(username=self.project_member_name,
443-
password=self.project_member_pass)
444-
response = self.client.get(self.url, format='json')
445-
self.assertEqual(response.data['count'], self.main_project.documents.count())
504+
self._test_list(self.url,
505+
username=self.project_member_name,
506+
password=self.project_member_pass,
507+
expected_num_results=self.main_project.documents.count())
446508

447509
def test_allows_superuser_to_create_doc(self):
448510
self.client.login(username=self.super_user_name,
@@ -584,7 +646,7 @@ def doCleanups(cls):
584646
remove_all_role_mappings()
585647

586648

587-
class TestAnnotationListAPI(APITestCase):
649+
class TestAnnotationListAPI(APITestCase, TestUtilsMixin):
588650

589651
@classmethod
590652
def setUpTestData(cls):
@@ -1309,33 +1371,54 @@ def test_can_download_plain_text(self):
13091371
expected_status=status.HTTP_400_BAD_REQUEST)
13101372

13111373

1312-
class TestStatisticsAPI(APITestCase):
1374+
class TestStatisticsAPI(APITestCase, TestUtilsMixin):
13131375

13141376
@classmethod
13151377
def setUpTestData(cls):
13161378
cls.super_user_name = 'super_user_name'
13171379
cls.super_user_pass = 'super_user_pass'
1380+
cls.other_user_name = 'other_user_name'
1381+
cls.other_user_pass = 'other_user_pass'
13181382
create_default_roles()
13191383
# Todo: change super_user to project_admin.
13201384
super_user = User.objects.create_superuser(username=cls.super_user_name,
13211385
password=cls.super_user_pass,
13221386
13231387

1324-
main_project = mommy.make('TextClassificationProject', users=[super_user])
1325-
doc1 = mommy.make('Document', project=main_project)
1326-
mommy.make('Document', project=main_project)
1388+
other_user = User.objects.create_user(username=cls.other_user_name,
1389+
password=cls.other_user_pass,
1390+
1391+
1392+
cls.project = mommy.make('TextClassificationProject', users=[super_user, other_user])
1393+
doc1 = mommy.make('Document', project=cls.project)
1394+
doc2 = mommy.make('Document', project=cls.project)
13271395
mommy.make('DocumentAnnotation', document=doc1, user=super_user)
1328-
cls.url = reverse(viewname='statistics', args=[main_project.id])
1329-
cls.doc = Document.objects.filter(project=main_project)
1396+
mommy.make('DocumentAnnotation', document=doc2, user=other_user)
1397+
cls.url = reverse(viewname='statistics', args=[cls.project.id])
1398+
cls.doc = Document.objects.filter(project=cls.project)
1399+
1400+
assign_user_to_role(project_member=other_user, project=cls.project,
1401+
role_name=settings.ROLE_ANNOTATOR)
1402+
1403+
@classmethod
1404+
def doCleanups(cls):
1405+
remove_all_role_mappings()
13301406

13311407
def test_returns_exact_progress(self):
13321408
self.client.login(username=self.super_user_name,
13331409
password=self.super_user_pass)
13341410
response = self.client.get(self.url, format='json')
1335-
total = self.doc.count()
1336-
remaining = self.doc.filter(doc_annotations__isnull=True).count()
1337-
self.assertEqual(response.data['total'], total)
1338-
self.assertEqual(response.data['remaining'], remaining)
1411+
self.assertEqual(response.data['total'], 2)
1412+
self.assertEqual(response.data['remaining'], 1)
1413+
1414+
def test_returns_exact_progress_with_collaborative_annotation(self):
1415+
self._patch_project(self.project, 'collaborative_annotation', True)
1416+
1417+
self.client.login(username=self.other_user_name,
1418+
password=self.other_user_pass)
1419+
response = self.client.get(self.url, format='json')
1420+
self.assertEqual(response.data['total'], 2)
1421+
self.assertEqual(response.data['remaining'], 0)
13391422

13401423
def test_returns_user_count(self):
13411424
self.client.login(username=self.super_user_name,

app/api/views.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from django.contrib.auth.models import User
33
from django.shortcuts import get_object_or_404, redirect
44
from django_filters.rest_framework import DjangoFilterBackend
5-
from django.db.models import Count, F
5+
from django.db.models import Count, F, Q
66
from libcloud.base import DriverType, get_driver
77
from libcloud.storage.types import ContainerDoesNotExistError, ObjectDoesNotExistError
88
from rest_framework import generics, filters, status
@@ -90,9 +90,11 @@ def progress(self, project):
9090
docs = project.documents
9191
annotation_class = project.get_annotation_class()
9292
total = docs.count()
93-
done = annotation_class.objects.filter(document_id__in=docs.all(),
94-
user_id=self.request.user).\
95-
aggregate(Count('document', distinct=True))['document__count']
93+
annotation_filter = Q(document_id__in=docs.all())
94+
if not project.collaborative_annotation:
95+
annotation_filter &= Q(user_id=self.request.user)
96+
done = annotation_class.objects.filter(annotation_filter)\
97+
.aggregate(Count('document', distinct=True))['document__count']
9698
remaining = total - done
9799
return {'total': total, 'remaining': remaining}
98100

0 commit comments

Comments
 (0)