Skip to content
Snippets Groups Projects
Commit c2c77e59 authored by Erwan Rouchet's avatar Erwan Rouchet
Browse files

Merge branch 'clear-process' into 'master'

New ClearProcess endpoint

Closes #1045

See merge request !1717
parents b1ae81a1 988a3a32
No related branches found
No related tags found
1 merge request!1717New ClearProcess endpoint
......@@ -23,6 +23,7 @@ from rest_framework import permissions, status
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
from rest_framework.generics import (
CreateAPIView,
DestroyAPIView,
GenericAPIView,
ListAPIView,
ListCreateAPIView,
......@@ -1741,3 +1742,38 @@ class ApplyProcessTemplate(ProcessACLMixin, WorkerACLMixin, CreateAPIView):
status=status.HTTP_200_OK,
data=DataImportSerializer(dataimport, context={'request': self.request}).data
)
@extend_schema_view(
delete=extend_schema(
operation_id='ClearProcess',
tags=['imports'],
responses={
204: None,
},
)
)
class ClearProcess(ProcessACLMixin, DestroyAPIView):
"""
Remove all templates and/or workers from a (not started) process
"""
permission_classes = (IsVerified, )
queryset = DataImport.objects.all()
def check_object_permissions(self, request, process):
super().check_object_permissions(request, process)
access_level = self.process_access_level(process)
required_access = Role.Admin.value
if not access_level:
raise NotFound
if access_level < required_access:
raise PermissionDenied(detail='You do not have a sufficient access level to this process.')
if process.workflow_id is not None:
raise ValidationError({'__all__': ['A process can only be cleared before getting started.']})
def destroy(self, request, *args, **kwargs):
process = self.get_object()
process.clear()
return Response(status=status.HTTP_204_NO_CONTENT)
......@@ -395,6 +395,11 @@ class DataImport(IndexableModel):
else:
self.start()
def clear(self):
self.worker_runs.all().delete()
self.template_id = None
self.save()
class DataImportElement(models.Model):
"""
......
......@@ -65,6 +65,7 @@ class TestImports(FixtureAPITestCase):
)
cls.page_type = ElementType.objects.get(corpus=cls.corpus, slug='page')
cls.recognizer = WorkerVersion.objects.get(worker__slug='reco')
cls.dla = WorkerVersion.objects.get(worker__slug='dla')
cls.version_gpu = WorkerVersion.objects.get(worker__slug='worker-gpu')
cls.workers_process = cls.corpus.imports.get(mode=DataImportMode.Workers)
cls.version_with_model = WorkerVersion.objects.get(worker__slug='generic')
......@@ -1469,3 +1470,133 @@ class TestImports(FixtureAPITestCase):
}
]
)
def test_clear_process(self):
process = self.corpus.imports.create(
creator=self.user,
mode=DataImportMode.Workers,
element_type=self.corpus.types.get(slug='page')
)
process.worker_runs.create(version=self.dla, parents=[], configuration=None)
process.worker_runs.create(version=self.recognizer, parents=[], configuration=None)
self.assertEqual(process.worker_runs.count(), 2)
self.client.force_login(self.user)
with self.assertNumQueries(9):
response = self.client.delete(reverse('api:clear-process', kwargs={'pk': str(process.id)}))
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
process.refresh_from_db()
self.assertEqual(process.worker_runs.count(), 0)
def test_clear_process_requires_login(self):
process = self.corpus.imports.create(
creator=self.user,
mode=DataImportMode.Workers,
element_type=self.corpus.types.get(slug='page')
)
process.worker_runs.create(version=self.dla, parents=[], configuration=None)
process.worker_runs.create(version=self.recognizer, parents=[], configuration=None)
self.assertEqual(process.worker_runs.count(), 2)
with self.assertNumQueries(0):
response = self.client.delete(reverse('api:clear-process', kwargs={'pk': str(process.id)}))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_clear_process_requires_verified(self):
process = self.corpus.imports.create(
creator=self.user,
mode=DataImportMode.Workers,
element_type=self.corpus.types.get(slug='page')
)
process.worker_runs.create(version=self.dla, parents=[], configuration=None)
process.worker_runs.create(version=self.recognizer, parents=[], configuration=None)
self.assertEqual(process.worker_runs.count(), 2)
self.user.verified_email = False
self.user.save()
self.client.force_login(self.user)
with self.assertNumQueries(2):
response = self.client.delete(reverse('api:clear-process', kwargs={'pk': str(process.id)}))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertDictEqual(response.json(), {'detail': 'You do not have permission to perform this action.'})
def test_clear_process_does_not_exist(self):
process = self.corpus.imports.create(
creator=self.user,
mode=DataImportMode.Workers,
element_type=self.corpus.types.get(slug='page')
)
process.worker_runs.create(version=self.dla, parents=[], configuration=None)
process.worker_runs.create(version=self.recognizer, parents=[], configuration=None)
self.assertEqual(process.worker_runs.count(), 2)
fake = uuid.uuid4()
self.client.force_login(self.user)
with self.assertNumQueries(3):
response = self.client.delete(reverse('api:clear-process', kwargs={'pk': str(fake)}))
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_clear_process_not_unscheduled(self):
"""
Cannot clear a process that has already started
"""
process = self.corpus.imports.create(
creator=self.user,
mode=DataImportMode.Workers,
element_type=self.corpus.types.get(slug='page')
)
process.worker_runs.create(version=self.dla, parents=[], configuration=None)
process.worker_runs.create(version=self.recognizer, parents=[], configuration=None)
self.assertEqual(process.worker_runs.count(), 2)
process.start()
process.workflow.tasks.update(state=State.Running)
self.assertEqual(process.state, State.Running)
self.client.force_login(self.user)
with self.assertNumQueries(7):
response = self.client.delete(reverse('api:clear-process', kwargs={'pk': str(process.id)}))
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertDictEqual(response.json(), {'__all__': ['A process can only be cleared before getting started.']})
def test_clear_process_unscheduled_workflow(self):
"""
Cannot clear a process that has a workflow, even unscheduled
"""
process = self.corpus.imports.create(
creator=self.user,
mode=DataImportMode.Workers,
element_type=self.corpus.types.get(slug='page')
)
process.worker_runs.create(version=self.dla, parents=[], configuration=None)
process.worker_runs.create(version=self.recognizer, parents=[], configuration=None)
self.assertEqual(process.worker_runs.count(), 2)
process.start()
self.assertEqual(process.state, State.Unscheduled)
self.client.force_login(self.user)
with self.assertNumQueries(7):
response = self.client.delete(reverse('api:clear-process', kwargs={'pk': str(process.id)}))
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertDictEqual(response.json(), {'__all__': ['A process can only be cleared before getting started.']})
def test_clear_process_requires_permissions(self):
process = self.corpus.imports.create(
creator=self.user,
mode=DataImportMode.Workers,
element_type=self.corpus.types.get(slug='page')
)
process.worker_runs.create(version=self.dla, parents=[], configuration=None)
process.worker_runs.create(version=self.recognizer, parents=[], configuration=None)
self.assertEqual(process.worker_runs.count(), 2)
user2 = User.objects.create_user('email@mail.com', 'bob')
user2.verified_email = True
user2.save()
self.corpus.memberships.create(user=user2, level=Role.Contributor.value)
self.client.force_login(user2)
with self.assertNumQueries(6):
response = self.client.delete(reverse('api:clear-process', kwargs={'pk': str(process.id)}))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertDictEqual(response.json(), {'detail': 'You do not have a sufficient access level to this process.'})
......@@ -4,6 +4,7 @@ from django.views.generic.base import RedirectView
from arkindex.dataimport.api import (
ApplyProcessTemplate,
AvailableRepositoriesList,
ClearProcess,
CorpusWorkersActivity,
CorpusWorkerVersionList,
CorpusWorkflow,
......@@ -239,6 +240,7 @@ api = [
path('process/<uuid:pk>/activity-stats/', ProcessWorkersActivity.as_view(), name='process-activity-stats'),
path('process/<uuid:pk>/template/', CreateProcessTemplate.as_view(), name='create-process-template'),
path('process/<uuid:pk>/apply/', ApplyProcessTemplate.as_view(), name='apply-process-template'),
path('imports/<uuid:pk>/clear/', ClearProcess.as_view(), name='clear-process'),
# ML models training
path('modelversion/<uuid:pk>/', ModelVersionsRetrieve.as_view(), name='model-version-retrieve'),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment