diff --git a/ESSArch_Core/frontend/static/frontend/views/remove_validation_modal.html b/ESSArch_Core/frontend/static/frontend/views/remove_validation_modal.html
new file mode 100644
index 000000000..d8528a2ce
--- /dev/null
+++ b/ESSArch_Core/frontend/static/frontend/views/remove_validation_modal.html
@@ -0,0 +1,30 @@
+
+
{{'VALIDATION_VIEW.REMOVE_VALIDATION' | translate}}
+
+
+
{{'VALIDATION' | translate}}
+
+
+
+
+ | {{'LABEL' | translate}} |
+ {{'VALIDATION_VIEW.VALIDATOR' | translate}} |
+
+
+
+
+ | {{$ctrl.data.validation.label}} |
+
+ {{$ctrl.data.validation.validator ? $ctrl.data.validation.validator.name : ('NONE_SELECTED' | translate)}}
+ |
+
+
+
+
+
+
diff --git a/ESSArch_Core/frontend/static/frontend/views/validation_view.html b/ESSArch_Core/frontend/static/frontend/views/validation_view.html
new file mode 100644
index 000000000..24739859a
--- /dev/null
+++ b/ESSArch_Core/frontend/static/frontend/views/validation_view.html
@@ -0,0 +1,89 @@
+
diff --git a/ESSArch_Core/ip/tests/test_views.py b/ESSArch_Core/ip/tests/test_views.py
index ff7cd75f6..a98bdf58c 100644
--- a/ESSArch_Core/ip/tests/test_views.py
+++ b/ESSArch_Core/ip/tests/test_views.py
@@ -2,12 +2,13 @@
import shutil
import tempfile
+from celery import states as celery_states
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
from rest_framework import status
-from rest_framework.test import APIClient
+from rest_framework.test import APIClient, APITestCase
from ESSArch_Core.auth.models import (
Group,
@@ -16,6 +17,8 @@
GroupType,
)
from ESSArch_Core.configuration.models import Path, StoragePolicy
+from ESSArch_Core.exceptions import ValidationError
+from ESSArch_Core.fixity.checksum import calculate_checksum
from ESSArch_Core.ip.models import InformationPackage, Workarea
from ESSArch_Core.profiles.models import SubmissionAgreement
from ESSArch_Core.storage.models import (
@@ -28,6 +31,8 @@
StorageObject,
StorageTarget,
)
+from ESSArch_Core.testing.runner import TaskRunner
+from ESSArch_Core.WorkflowEngine.models import ProcessTask
User = get_user_model()
@@ -316,3 +321,165 @@ def test_get_migratable_new_storage_target(self):
ip_exists = InformationPackage.objects.migratable().exists()
self.assertFalse(ip_exists)
+
+
+class InformationPackageValidationTests(APITestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.user = User.objects.create(username='superuser', is_superuser=True)
+
+ def setUp(self):
+ self.datadir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.datadir)
+
+ Path.objects.create(entity='temp', value=tempfile.mkdtemp(dir=self.datadir))
+
+ self.ip = InformationPackage.objects.create(
+ object_path=tempfile.mkdtemp(dir=self.datadir),
+ )
+
+ @TaskRunner()
+ def test_validate(self):
+ self.client.force_authenticate(self.user)
+ url = reverse('informationpackage-validate', args=(str(self.ip.pk),))
+
+ os.makedirs(os.path.join(self.ip.object_path, 'content'))
+
+ with open(os.path.join(self.ip.object_path, 'content', 'foo.txt'), 'w') as f:
+ f.write('hello')
+
+ chksum = calculate_checksum(os.path.join(self.ip.object_path, 'content', 'foo.txt'), 'SHA-224')
+ with open(os.path.join(self.ip.object_path, 'metadata.xml'), 'w') as f:
+ f.write('
')
+
+ with self.subTest('invalid file path'):
+ response = self.client.post(url, {
+ 'validators': [
+ {
+ 'name': 'checksum',
+ 'path': 'foo.txt',
+ 'context': 'checksum_str',
+ 'options': {
+ 'expected': chksum,
+ 'algorithm': 'SHA-224',
+ }
+ },
+ ],
+ })
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(ProcessTask.objects.exists())
+
+ with self.subTest('valid path and expected value'):
+ response = self.client.post(url, {
+ 'validators': [
+ {
+ 'name': 'checksum',
+ 'path': 'content/foo.txt',
+ 'context': 'checksum_str',
+ 'options': {
+ 'expected': chksum,
+ 'algorithm': 'SHA-224',
+ }
+ },
+ ],
+ })
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.SUCCESS).count(), 1)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.FAILURE).count(), 0)
+
+ with self.subTest('valid path and unexpected value'):
+ with self.assertRaises(ValidationError):
+ response = self.client.post(url, {
+ 'validators': [
+ {
+ 'name': 'checksum',
+ 'path': 'content/foo.txt',
+ 'context': 'checksum_str',
+ 'options': {
+ 'expected': 'incorrect',
+ 'algorithm': 'SHA-224',
+ }
+ },
+ ],
+ })
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.SUCCESS).count(), 1)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.FAILURE).count(), 1)
+
+ with self.subTest('multiple validators'):
+ with self.assertRaises(ValidationError):
+ response = self.client.post(url, {
+ 'information_package': str(self.ip.pk),
+ 'validators': [
+ {
+ 'name': 'checksum',
+ 'path': 'content/foo.txt',
+ 'context': 'checksum_str',
+ 'options': {
+ 'expected': chksum,
+ 'algorithm': 'SHA-224',
+ }
+ },
+ {
+ 'name': 'diff_check',
+ 'path': '',
+ 'context': 'metadata.xml',
+ 'options': {
+ 'recursive': True,
+ 'default_algorithm': 'SHA-224',
+ }
+ },
+ ],
+ })
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.SUCCESS).count(), 2)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.FAILURE).count(), 2)
+
+ with open(os.path.join(self.ip.object_path, 'metadata.xml'), 'w') as f:
+ f.write(f'
')
+
+ response = self.client.post(url, {
+ 'validators': [
+ {
+ 'name': 'checksum',
+ 'path': 'content/foo.txt',
+ 'context': 'checksum_str',
+ 'options': {
+ 'expected': chksum,
+ 'algorithm': 'SHA-224',
+ }
+ },
+ {
+ 'name': 'diff_check',
+ 'path': '',
+ 'context': 'metadata.xml',
+ 'options': {
+ 'recursive': True,
+ 'default_algorithm': 'SHA-224',
+ }
+ },
+ ],
+ })
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.SUCCESS).count(), 4)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.FAILURE).count(), 2)
+
+ with open(os.path.join(self.ip.object_path, 'metadata.xml'), 'w') as f:
+ f.write(f'
')
+
+ response = self.client.post(url, {
+ 'validators': [
+ {
+ 'name': 'diff_check',
+ 'path': 'content',
+ 'context': 'metadata.xml',
+ 'options': {
+ 'recursive': True,
+ 'default_algorithm': 'SHA-224',
+ }
+ },
+ ],
+ })
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.SUCCESS).count(), 5)
+ self.assertEqual(ProcessTask.objects.filter(status=celery_states.FAILURE).count(), 2)
diff --git a/ESSArch_Core/ip/views.py b/ESSArch_Core/ip/views.py
index 2fbbc14e6..5885a3383 100644
--- a/ESSArch_Core/ip/views.py
+++ b/ESSArch_Core/ip/views.py
@@ -64,8 +64,12 @@
from ESSArch_Core.essxml.util import get_objectpath, parse_submit_description
from ESSArch_Core.exceptions import Conflict, NoFileChunksFound
from ESSArch_Core.fixity.format import FormatIdentifier
+from ESSArch_Core.fixity.serializers import ValidatorWorkflowSerializer
from ESSArch_Core.fixity.transformation import AVAILABLE_TRANSFORMERS
-from ESSArch_Core.fixity.validation import AVAILABLE_VALIDATORS
+from ESSArch_Core.fixity.validation import (
+ AVAILABLE_VALIDATORS,
+ get_backend as get_validator,
+)
from ESSArch_Core.fixity.validation.backends.checksum import ChecksumValidator
from ESSArch_Core.ip.filters import (
AgentFilter,
@@ -1924,46 +1928,48 @@ def unlock_profile(self, request, pk=None):
)
})
- @transaction.atomic
@action(detail=True, methods=['post'], url_path='validate')
def validate(self, request, pk=None):
ip = self.get_object()
- prepare = Path.objects.get(entity="ingest_workarea").value
- xmlfile = os.path.join(prepare, "%s.xml" % pk)
+ request.data['information_package'] = str(ip.pk)
+ serializer = ValidatorWorkflowSerializer(data=request.data, context={'request': request})
+ serializer.is_valid(raise_exception=True)
+ workflow_spec = []
+ ip = serializer.validated_data['information_package']
- step = ProcessStep.objects.create(
- name="Validation",
- information_package=ip
- )
+ for validator in serializer.validated_data['validators']:
+ name = validator['name']
+ klass = get_validator(name)
- step.add_tasks(
- ProcessTask.objects.create(
- name="ESSArch_Core.tasks.ValidateXMLFile",
- params={
- "xml_filename": xmlfile
- },
- log=EventIP,
- information_package=ip,
- responsible=self.request.user,
- ),
- ProcessTask.objects.create(
- name="ESSArch_Core.tasks.ValidateFiles",
- params={
- "mets_path": xmlfile,
- "validate_fileformat": True,
- "validate_integrity": True,
- },
- log=EventIP,
- processstep_pos=0,
- information_package=ip,
- responsible=self.request.user,
- )
- )
+ options = validator['options']
+ if validator['path']:
+ path = os.path.join(ip.object_path, validator['path'])
+ else:
+ path = ip.object_path
- step.run()
+ task_spec = {
+ 'name': 'ESSArch_Core.fixity.validation.tasks.Validate',
+ 'label': 'Validate using {}'.format(klass.label),
+ 'args': [name, path],
+ 'params': {'context': validator['context'], 'options': options},
+ }
- return Response("Validating IP")
+ workflow_spec.append(task_spec)
+
+ with transaction.atomic():
+ step = {
+ 'step': True,
+ 'name': serializer.validated_data['purpose'],
+ 'parallel': True,
+ 'children': workflow_spec
+ }
+ workflow = create_workflow([step], ip=ip, name='Validation')
+
+ workflow.run()
+
+ headers = self.get_success_headers(serializer.data)
+ return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, *args, **kwargs):
ip = self.get_object()
diff --git a/ESSArch_Core/tasks.py b/ESSArch_Core/tasks.py
index a3c51d14e..acddd1ad7 100644
--- a/ESSArch_Core/tasks.py
+++ b/ESSArch_Core/tasks.py
@@ -33,7 +33,6 @@
import requests
from django.conf import settings
from django.contrib.auth import get_user_model
-from django.core.exceptions import ValidationError
from django.core.mail import EmailMessage
from django.db import transaction
from django.utils import timezone
@@ -53,7 +52,6 @@
findElementWithoutNamespace,
)
from ESSArch_Core.essxml.util import find_pointers, get_premis_ref
-from ESSArch_Core.fixity import validation
from ESSArch_Core.fixity.models import Validation
from ESSArch_Core.fixity.transformation import get_backend as get_transformer
from ESSArch_Core.fixity.validation.backends.xml import (
@@ -316,66 +314,6 @@ def event_outcome_success(self, result, filename=None, format_name=None,
)
-class ValidateWorkarea(DBTask):
- queue = 'validation'
-
- def create_notification(self, ip):
- errcount = Validation.objects.filter(information_package=ip, passed=False, required=True).count()
-
- if errcount:
- Notification.objects.create(
- message='Validation of "{ip}" failed with {errcount} error(s)'.format(
- ip=ip.object_identifier_value, errcount=errcount
- ),
- level=logging.ERROR,
- user_id=self.responsible,
- refresh=True
- )
- else:
- Notification.objects.create(
- message='"{ip}" was successfully validated'.format(
- ip=ip.object_identifier_value
- ),
- level=logging.INFO,
- user_id=self.responsible,
- refresh=True
- )
-
- def run(self, workarea, validators, stop_at_failure=True):
- workarea = Workarea.objects.get(pk=workarea)
- workarea.successfully_validated = {}
-
- for validator in validators:
- workarea.successfully_validated[validator] = None
-
- workarea.save(update_fields=['successfully_validated'])
- ip = workarea.ip
- sa = ip.submission_agreement
- validation_profile = ip.get_profile('validation')
- profile_data = fill_specification_data(data=ip.get_profile_data('validation'), sa=sa, ip=ip)
- responsible = User.objects.get(pk=self.responsible)
-
- try:
- validation.validate_path(workarea.path, validators, validation_profile, data=profile_data, ip=ip,
- task=self.get_processtask(), stop_at_failure=stop_at_failure,
- responsible=responsible)
- except ValidationError:
- self.create_notification(ip)
- else:
- self.create_notification(ip)
- finally:
- validations = ip.validation_set.all()
- failed_validators = validations.values('validator').filter(
- passed=False, required=True
- ).values_list('validator', flat=True)
-
- for k, _v in workarea.successfully_validated.items():
- class_name = validation.AVAILABLE_VALIDATORS[k].split('.')[-1]
- workarea.successfully_validated[k] = class_name not in failed_validators
-
- workarea.save(update_fields=['successfully_validated'])
-
-
class TransformWorkarea(DBTask):
def run(self, backend, workarea):
workarea = Workarea.objects.select_related('ip__submission_agreement').get(pk=workarea)
@@ -440,9 +378,9 @@ def run(self, path, xmlfile, skip_files=None, relpath=None):
else:
rootdir = os.path.dirname(path)
- ip = InformationPackage.objects.get(pk=self.ip)
+ ip = self.get_information_package()
validator = DiffCheckValidator(context=xmlfile, exclude=skip_files, options={'rootdir': rootdir},
- task=self.get_processtask(), ip=self.ip, responsible=ip.responsible)
+ task=self.get_processtask(), ip=ip, responsible=ip.responsible)
validator.validate(path)
def event_outcome_success(self, result, path, xmlfile, skip_files=None, relpath=None):
@@ -459,7 +397,7 @@ class CompareXMLFiles(DBTask):
def run(self, first, second, rootdir=None, recursive=True):
Validation.objects.filter(task=self.get_processtask()).delete()
first, second = self.parse_params(first, second)
- ip = InformationPackage.objects.get(pk=self.ip)
+ ip = self.get_information_package()
if rootdir is None:
rootdir = ip.object_path
else:
@@ -469,7 +407,7 @@ def run(self, first, second, rootdir=None, recursive=True):
context=first,
options={'rootdir': rootdir, 'recursive': recursive},
task=self.get_processtask(),
- ip=self.ip,
+ ip=ip,
responsible=ip.responsible,
)
validator.validate(second)