diff --git a/monitoring/publishing/static/stylesheet.css b/monitoring/publishing/static/stylesheet.css index a651e07..603c628 100644 --- a/monitoring/publishing/static/stylesheet.css +++ b/monitoring/publishing/static/stylesheet.css @@ -186,3 +186,15 @@ li { background-color: #8D8D8D; color: white; } + +textarea { + vertical-align: top; +} + +button#validate-button { + background-color: #e0e0e0; + border-color: #b0b0b0; + cursor: pointer; + font-weight: bold; + padding: 5px 10px; +} diff --git a/monitoring/settings.py b/monitoring/settings.py index 5714c40..e10b183 100644 --- a/monitoring/settings.py +++ b/monitoring/settings.py @@ -95,6 +95,7 @@ 'monitoring.availability', 'monitoring.benchmarks', 'monitoring.iris', + 'monitoring.validator', ] REST_FRAMEWORK = { diff --git a/monitoring/templates/home.html b/monitoring/templates/home.html index a5be5f3..1d8c481 100644 --- a/monitoring/templates/home.html +++ b/monitoring/templates/home.html @@ -42,5 +42,12 @@

Return format

For example: {% url 'gridsitesync-detail' 'RAL-LCG2' %}?format=json

+

Record Validator

+

+ diff --git a/monitoring/urls.py b/monitoring/urls.py index 374a22e..185be43 100644 --- a/monitoring/urls.py +++ b/monitoring/urls.py @@ -10,4 +10,5 @@ path('benchmarks/', include('monitoring.benchmarks.urls')), path('iris/', include('monitoring.iris.urls')), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), + path('validator/', include ('monitoring.validator.urls')), ] diff --git a/monitoring/validator/__init__.py b/monitoring/validator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/monitoring/validator/admin.py b/monitoring/validator/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/monitoring/validator/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/monitoring/validator/apps.py b/monitoring/validator/apps.py new file mode 100644 index 0000000..a754311 --- /dev/null +++ b/monitoring/validator/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ValidatorConfig(AppConfig): + name = 'monitoring.validator' diff --git a/monitoring/validator/migrations/__init__.py b/monitoring/validator/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/monitoring/validator/models.py b/monitoring/validator/models.py new file mode 100644 index 0000000..e69de29 diff --git a/monitoring/validator/templates/validator/validator_index.html b/monitoring/validator/templates/validator/validator_index.html new file mode 100644 index 0000000..748741a --- /dev/null +++ b/monitoring/validator/templates/validator/validator_index.html @@ -0,0 +1,60 @@ +{% load static %} + + + + + + + Record Validator + + + + + +

Record Validator

+ +
+ {% csrf_token %} + + +

Selecting 'All' means the type is determined by the record header.

+

If you select a specific record type, the header will need to be removed from the record input.

+

v0.4 records are the newer record type - they require dictionary fields for benchmark data.

+
+ + +

+ +

+
+ + + +
+ {% if output %} +

Validation Output

+ {{ output }} + {% endif %} +
+ + diff --git a/monitoring/validator/tests.py b/monitoring/validator/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/monitoring/validator/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/monitoring/validator/urls.py b/monitoring/validator/urls.py new file mode 100644 index 0000000..32b80d6 --- /dev/null +++ b/monitoring/validator/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from monitoring.validator import views + +urlpatterns = [ + path('', views.index, name="validator"), +] + diff --git a/monitoring/validator/views.py b/monitoring/validator/views.py new file mode 100644 index 0000000..1490d8a --- /dev/null +++ b/monitoring/validator/views.py @@ -0,0 +1,89 @@ +from django.shortcuts import render +from django.views.decorators.http import require_http_methods + +# Apel record-checking class imports +from apel.db.loader.record_factory import RecordFactory, RecordFactoryException +from apel.db.records.record import InvalidRecordException + +# Apel record-type class imports +from apel.db.records.job import JobRecord, JobRecord04 +from apel.db.records.summary import SummaryRecord, SummaryRecord04 +from apel.db.records.normalised_summary import NormalisedSummaryRecord, NormalisedSummaryRecord04 +from apel.db.records.sync import SyncRecord +from apel.db.records.cloud import CloudRecord +from apel.db.records.cloud_summary import CloudSummaryRecord + + +@require_http_methods(["GET", "POST"]) +def index(request): + """ + Validates inputted records using the Apel record validation methods. + It either validates a record against a specific type or against all types, depending on what record_type + option was chosen on the html template. The default is `All`. + The input record, record type and validation output are then returned to the html template as context on get + request, so that the html page retains its information/context when refreshing the page or submitting the form. + """ + template_name = "validator/validator_index.html" + input_record = "" + record_type = "All" + output = "" + + # On form submission, trigger record validation + if request.method == "POST": + input_record = request.POST.get("input_record", "") + record_type = request.POST.get("record_type", "") + output = validate(input_record, record_type) + + context = { + "input_record": input_record, + "record_type": record_type, + "output": output, + } + + return render(request, template_name, context) + + +def validate(record: str, record_type: str) -> str: + """ + Validated record(s) and record_type passed in from the html page template. + If record type is all, make use of the create_records apel method (expects a record header). + Else, make use of the _create_record_objects apel method (expects there to be no record header). + If the record is valid, return a "valid record" string. + If the record is invalid, an InvalidRecordException or RecordFactoryException is raised by the Apel methods. + Catch these exceptions and return the exception information. + """ + if not record: + return "Please enter a record to be validated." + + record = record.strip() + + # Map record_type string to record_type class + # String is always exact as determined through html form selection option + record_map = { + "JobRecord": JobRecord, + "JobRecord04": JobRecord04, + "SummaryRecord": SummaryRecord, + "SummaryRecord04": SummaryRecord04, + "NormalisedSummaryRecord": NormalisedSummaryRecord, + "NormalisedSummaryRecord04": NormalisedSummaryRecord04, + "SyncRecord": SyncRecord, + "CloudRecord": CloudRecord, + "CloudSummaryRecord": CloudSummaryRecord, + } + + try: + recordFactory = RecordFactory() + + if record_type == "All": + result = recordFactory.create_records(record) + else: + record_class = record_map[record_type] + result = recordFactory._create_record_objects(record, record_class) + + if "Record object at" in str(result): + return "Record(s) valid!" + + return str(result) + + except (InvalidRecordException, RecordFactoryException) as e: + return str(e) diff --git a/requirements.txt b/requirements.txt index 69f5922..b8bae58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ # Pin packages to support and work with py3.6. +apel Django==3.2.25 djangorestframework==3.15.1 pytz==2025.2