From 76bb233b70ffe08c5cbed1f127784574fec0f4b8 Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Fri, 4 Mar 2016 20:08:29 +0000 Subject: [PATCH] Refactored draft submission checks so that new checkers can be slotted in through a configuration in settings.py. Refactored the calling of idnits to use the new API, and added a pyang validation check. - Legacy-Id: 10894 --- ietf/checks.py | 44 +++++ ietf/settings.py | 8 +- ietf/submit/admin.py | 8 +- ietf/submit/checkers.py | 171 ++++++++++++++++++ .../submit/migrations/0004_submissioncheck.py | 31 ++++ .../migrations/0005_auto_20160227_0809.py | 31 ++++ ietf/submit/models.py | 18 ++ ietf/submit/resources.py | 20 +- ietf/submit/test_submission.txt | 106 ++++++++++- ietf/submit/test_submission.xml | 114 +++++++++++- ietf/submit/tests.py | 3 +- ietf/submit/utils.py | 18 -- ietf/submit/views.py | 30 ++- .../field_help_text_and_errors.html | 2 + ietf/templates/submit/submission_status.html | 62 ++++--- ietf/templates/submit/upload_submission.html | 1 + ietf/utils/test_smtpserver.py | 1 + ietf/utils/test_utils.py | 1 - 18 files changed, 607 insertions(+), 62 deletions(-) create mode 100644 ietf/submit/checkers.py create mode 100644 ietf/submit/migrations/0004_submissioncheck.py create mode 100644 ietf/submit/migrations/0005_auto_20160227_0809.py diff --git a/ietf/checks.py b/ietf/checks.py index 49a41f7da..707918edf 100644 --- a/ietf/checks.py +++ b/ietf/checks.py @@ -2,6 +2,7 @@ import os from django.conf import settings from django.core import checks +from django.utils.module_loading import import_string @checks.register('directories') def check_cdn_directory_exists(app_configs, **kwargs): @@ -98,3 +99,46 @@ def check_id_submission_files(app_configs, **kwargs): id = "datatracker.E0007", )) return errors + +@checks.register('submission-checkers') +def check_id_submission_checkers(app_configs, **kwargs): + errors = [] + for checker_path in settings.IDSUBMIT_CHECKER_CLASSES: + try: + checker_class = import_string(checker_path) + except Exception as e: + errors.append(checks.Critical( + "An exception was raised when trying to import the draft submission" + "checker class '%s':\n %s" % (checker_path, e), + hint = "Please check that the class exists and can be imported.", + id = "datatracker.E0008", + )) + try: + checker = checker_class() + except Exception as e: + errors.append(checks.Critical( + "An exception was raised when trying to instantiate the draft submission" + "checker class '%s': %s" % (checker_path, e), + hint = "Please check that the class can be instantiated.", + id = "datatracker.E0009", + )) + continue + for attr in ('name',): + if not hasattr(checker, attr): + errors.append(checks.Critical( + "The draft submission checker '%s' has no attribute '%s', which is required" % (checker_path, attr), + hint = "Please update the class.", + id = "datatracker.E0010", + )) + checker_methods = ("check_file_txt", "check_file_xml", "check_fragment_txt", "check_fragment_xml", ) + for method in checker_methods: + if hasattr(checker, method): + break + else: + errors.append(checks.Critical( + "The draft submission checker '%s' has no recognised checker method; " + "should be one or more of %s." % (checker_path, checker_methods), + hint = "Please update the class.", + id = "datatracker.E0011", + )) + return errors diff --git a/ietf/settings.py b/ietf/settings.py index fab27f0f7..257b100a7 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -472,10 +472,16 @@ IDSUBMIT_REPOSITORY_PATH = INTERNET_DRAFT_PATH IDSUBMIT_STAGING_PATH = '/a/www/www6s/staging/' IDSUBMIT_STAGING_URL = '//www.ietf.org/staging/' IDSUBMIT_IDNITS_BINARY = '/a/www/ietf-datatracker/scripts/idnits' +IDSUBMIT_PYANG_COMMAND = 'pyang -p %(workdir)s --verbose --ietf %(model)s' + +IDSUBMIT_CHECKER_CLASSES = ( + "ietf.submit.checkers.DraftIdnitsChecker", + "ietf.submit.checkers.DraftYangChecker", +) + IDSUBMIT_MANUAL_STAGING_DIR = '/tmp/' - IDSUBMIT_FILE_TYPES = ( 'txt', 'xml', diff --git a/ietf/submit/admin.py b/ietf/submit/admin.py index 740fb80cf..36d348f0d 100644 --- a/ietf/submit/admin.py +++ b/ietf/submit/admin.py @@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse as urlreverse from django.contrib import admin -from ietf.submit.models import Preapproval, Submission +from ietf.submit.models import Preapproval, Submission, SubmissionCheck class SubmissionAdmin(admin.ModelAdmin): list_display = ['id', 'draft_link', 'status_link', 'submission_date',] @@ -23,9 +23,13 @@ class SubmissionAdmin(admin.ModelAdmin): else: return instance.name draft_link.allow_tags = True - admin.site.register(Submission, SubmissionAdmin) +class SubmissionCheckAdmin(admin.ModelAdmin): + list_display = ['submission', 'time', 'checker', 'passed', 'errors', 'warnings', 'items'] + raw_id_fields = ['submission'] +admin.site.register(SubmissionCheck, SubmissionCheckAdmin) + class PreapprovalAdmin(admin.ModelAdmin): pass admin.site.register(Preapproval, PreapprovalAdmin) diff --git a/ietf/submit/checkers.py b/ietf/submit/checkers.py new file mode 100644 index 000000000..3f999b05d --- /dev/null +++ b/ietf/submit/checkers.py @@ -0,0 +1,171 @@ +# Copyright The IETF Trust 2016, All Rights Reserved + +import os +import re +from xym import xym +import shutil +import tempfile + +from django.conf import settings + +import debug # pyflakes:ignore + +from ietf.utils.pipe import pipe +from ietf.utils.log import log + +class DraftSubmissionChecker(): + name = "" + + def check_file_txt(self, text): + "Run checks on a text file" + raise NotImplementedError + + def check_file_xml(self, xml): + "Run checks on an xml file" + raise NotImplementedError + + def check_fragment_txt(self, text): + "Run checks on a fragment from a text file" + raise NotImplementedError + + def check_fragment_xml(self, xml): + "Run checks on a fragment from an xml file" + raise NotImplementedError + + +class DraftIdnitsChecker(object): + """ + Draft checker class for idnits. Idnits can only handle whole text files, + so only check_file_txt() is defined; check_file_xml and check_fragment_* + methods are undefined. + + Furthermore, idnits doesn't provide an error code or line-by-line errors, + so a bit of massage is needed in order to return the expected failure flag. + """ + name = "idnits check" + + def check_file_txt(self, path): + """ + Run an idnits check, and return a passed/failed indication, a message, + and error and warning messages. + + Error and warning list items are tuples: + (line_number, line_text, message) + """ + filename = os.path.basename(path) + result = {} + items = [] + errors = 0 + warnings = 0 + errstart = [' ** ', ' ~~ '] + warnstart = [' == ', ' -- '] + + + cmd = "%s --submitcheck --nitcount %s" % (settings.IDSUBMIT_IDNITS_BINARY, path) + code, out, err = pipe(cmd) + if code != 0 or out == "": + message = "idnits error: %s:\n Error %s: %s" %( cmd, code, err) + log(message) + passed = False + + else: + message = out + if re.search("\s+Summary:\s+0\s+|No nits found", out): + passed = True + else: + passed = False + + item = None + for line in message.splitlines(): + if line[:5] in (errstart + warnstart): + item = line.rstrip() + elif line.strip() == "" and item: + tuple = (None, None, item) + items.append(tuple) + if item[:5] in errstart: + errors += 1 + elif item[:5] in warnstart: + warnings += 1 + else: + raise RuntimeError("Unexpected state in idnits checker: item: %s, line: %s" % (item, line)) + item = None + elif item and line.strip() != "": + item += " " + line.strip() + else: + pass + result[filename] = { + "passed": passed, + "message": message, + "errors": errors, + "warnings":warnings, + "items": items, + } + + + return passed, message, errors, warnings, result + +class DraftYangChecker(object): + + name = "yang validation" + + def check_file_txt(self, path): + name = os.path.basename(path) + workdir = tempfile.mkdtemp() + results = {} + + extractor = xym.YangModuleExtractor(path, workdir, strict=True, debug_level = 0) + with open(path) as file: + try: + # This places the yang models as files in workdir + extractor.extract_yang_model(file.readlines()) + model_list = extractor.get_extracted_models() + except Exception as exc: + passed = False + message = exc + errors = [ (name, None, None, exc) ] + warnings = [] + return passed, message, errors, warnings + + for model in model_list: + path = os.path.join(workdir, model) + with open(path) as file: + text = file.readlines() + cmd = settings.IDSUBMIT_PYANG_COMMAND % {"workdir": workdir, "model": path, } + code, out, err = pipe(cmd) + errors = 0 + warnings = 0 + items = [] + if code > 0: + error_lines = err.splitlines() + for line in error_lines: + fn, lnum, msg = line.split(':', 2) + lnum = int(lnum) + line = text[lnum-1].rstrip() + items.append((lnum, line, msg)) + if 'error: ' in msg: + errors += 1 + if 'warning: ' in msg: + warnings += 1 + results[model] = { + "passed": code == 0, + "message": out+"No validation errors\n" if code == 0 else err, + "warnings": warnings, + "errors": errors, + "items": items, + } + + shutil.rmtree(workdir) + + ## For now, never fail because of failed yang validation. + if len(model_list): + passed = True + else: + passed = None + #passed = all( res["passed"] for res in results.values() ) + message = "\n\n".join([ "\n".join([model+':', res["message"]]) for model, res in results.items() ]) + errors = sum(res["errors"] for res in results.values() ) + warnings = sum(res["warnings"] for res in results.values() ) + items = [ e for res in results.values() for e in res["items"] ] + + return passed, message, errors, warnings, items + diff --git a/ietf/submit/migrations/0004_submissioncheck.py b/ietf/submit/migrations/0004_submissioncheck.py new file mode 100644 index 000000000..ffe5870ca --- /dev/null +++ b/ietf/submit/migrations/0004_submissioncheck.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import jsonfield + +class Migration(migrations.Migration): + + dependencies = [ + ('submit', '0003_auto_20150713_1104'), + ] + + operations = [ + migrations.CreateModel( + name='SubmissionCheck', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('time', models.DateTimeField(default=None, auto_now=True)), + ('checker', models.CharField(max_length=256, blank=True)), + ('passed', models.NullBooleanField(default=False)), + ('message', models.TextField(null=True, blank=True)), + ('warnings', models.IntegerField(null=True, blank=True, default=None)), + ('errors', models.IntegerField(null=True, blank=True, default=None)), + ('items', jsonfield.JSONField(null=True, blank=True, default=b'{}')), + ('submission', models.ForeignKey(related_name='checks', to='submit.Submission')), + ], + options={ + }, + bases=(models.Model,), + ), + ] diff --git a/ietf/submit/migrations/0005_auto_20160227_0809.py b/ietf/submit/migrations/0005_auto_20160227_0809.py new file mode 100644 index 000000000..1d8d268ad --- /dev/null +++ b/ietf/submit/migrations/0005_auto_20160227_0809.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import re + +from django.db import migrations + +def convert_to_submission_check(apps, schema_editor): + Submission = apps.get_model('submit','Submission') + SubmissionCheck = apps.get_model('submit','SubmissionCheck') + for s in Submission.objects.all(): + passed = re.search('\s+Summary:\s+0\s+|No nits found', s.idnits_message) != None + c = SubmissionCheck(submission=s, checker='idnits check', passed=passed, message=s.idnits_message) + c.save() + +def convert_from_submission_check(apps, schema_editor): + SubmissionCheck = apps.get_model('submit','SubmissionCheck') + for c in SubmissionCheck.objects.filter(checker='idnits check'): + c.submission.idnits_message = c.message + c.save() + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('submit', '0004_submissioncheck'), + ] + + operations = [ + migrations.RunPython(convert_to_submission_check, convert_from_submission_check) + ] diff --git a/ietf/submit/models.py b/ietf/submit/models.py index 8e7900990..158467f87 100644 --- a/ietf/submit/models.py +++ b/ietf/submit/models.py @@ -2,6 +2,7 @@ import re import datetime from django.db import models +import jsonfield from ietf.doc.models import Document from ietf.person.models import Person @@ -66,6 +67,23 @@ class Submission(models.Model): def existing_document(self): return Document.objects.filter(name=self.name).first() +class SubmissionCheck(models.Model): + time = models.DateTimeField(auto_now=True, default=None) # The default is to make makemigrations happy + submission = models.ForeignKey(Submission, related_name='checks') + checker = models.CharField(max_length=256, blank=True) + passed = models.NullBooleanField(default=False) + message = models.TextField(null=True, blank=True) + errors = models.IntegerField(null=True, blank=True, default=None) + warnings = models.IntegerField(null=True, blank=True, default=None) + items = jsonfield.JSONField(null=True, blank=True, default='{}') + # + def __unicode__(self): + return "%s submission check: %s: %s" % (self.checker, 'Passed' if self.passed else 'Failed', self.message[:48]+'...') + def has_warnings(self): + return self.warnings != '[]' + def has_errors(self): + return self.errors != '[]' + class SubmissionEvent(models.Model): submission = models.ForeignKey(Submission) time = models.DateTimeField(default=datetime.datetime.now) diff --git a/ietf/submit/resources.py b/ietf/submit/resources.py index 788780388..17f689565 100644 --- a/ietf/submit/resources.py +++ b/ietf/submit/resources.py @@ -1,6 +1,6 @@ # Autogenerated by the mkresources management command 2014-11-13 23:53 from tastypie.resources import ModelResource -from tastypie.fields import ToOneField +from tastypie.fields import ToOneField, ToManyField from tastypie.constants import ALL, ALL_WITH_RELATIONS from ietf import api @@ -28,6 +28,7 @@ from ietf.name.resources import DraftSubmissionStateNameResource class SubmissionResource(ModelResource): state = ToOneField(DraftSubmissionStateNameResource, 'state') group = ToOneField(GroupResource, 'group', null=True) + checks = ToManyField(SubmissionCheck, 'checks', null=True) class Meta: queryset = Submission.objects.all() serializer = api.Serializer() @@ -51,7 +52,6 @@ class SubmissionResource(ModelResource): "document_date": ALL, "submission_date": ALL, "submitter": ALL, - "idnits_message": ALL, "state": ALL_WITH_RELATIONS, "group": ALL_WITH_RELATIONS, } @@ -74,3 +74,19 @@ class SubmissionEventResource(ModelResource): } api.submit.register(SubmissionEventResource()) +class SubmissionCheckResource(ModelResource): + submission = ToOneField(SubmissionResource, 'submission') + class Meta: + queryset = SubmissionCheck.objects.all() + serializer = api.Serializer() + #resource_name = 'submissioncheck' + filtering = { + "id": ALL, + "checker": ALL, + "passed": ALL, + "warning": ALL, + "message": ALL, + "errors": ALL, + "submission": ALL_WITH_RELATIONS, + } +api.submit.register(SubmissionCheckResource()) diff --git a/ietf/submit/test_submission.txt b/ietf/submit/test_submission.txt index 9f16000a5..97c95b3c4 100644 --- a/ietf/submit/test_submission.txt +++ b/ietf/submit/test_submission.txt @@ -65,15 +65,115 @@ Table of Contents 3. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 2 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 2 -1. Introduction +1. Introduction This document describes a protocol for testing tests. -2. Security Considerations +2. Yang + + file "ietf-mpls@2015-10-16.yang" + + module ietf-mpls { + + namespace "urn:ietf:params:xml:ns:yang:ietf-mpls"; + + prefix "mpls"; + + import ietf-routing { + prefix "rt"; + } + + import ietf-interfaces { + prefix "if"; + } + + organization "TBD"; + + contact "TBD"; + + description + "This YANG module defines the essential components for the + management of the MPLS subsystem."; + + revision "2015-10-16" { + description + "Initial revision"; + reference "RFC 3031: A YANG Data Model for base MPLS"; + } + + typedef mpls-label { + type uint32 { + range "0..1048575"; + } + description + "The MPLS label range"; + } + + typedef percent { + type uint16 { + range "0 .. 100"; + } + description "Percentage"; + } + + grouping interface-mpls { + description "MPLS interface properties grouping"; + leaf enabled { + type boolean; + description + "'true' if mpls encapsulation is enabled on the + interface. 'false' if mpls encapsulation is enabled + on the interface."; + } + } + + augment "/rt:routing/rt:routing-instance" { + description "MPLS augmentation."; + container mpls { + description + "MPLS container, to be used as an augmentation target node + other MPLS sub-features config, e.g. MPLS static LSP, MPLS + LDP LSPs, and Trafic Engineering MPLS LSP Tunnels, etc."; + + list interface { + key "name"; + description "List of MPLS interfaces"; + leaf name { + type if:interface-ref; + description + "The name of a configured MPLS interface"; + } + container config { + description "Holds intended configuration"; + uses interface-mpls; + } + container state { + config false; + description "Holds inuse configuration"; + uses interface-mpls; + } + } + } + } + + augment "/rt:routing-state/rt:routing-instance" { + description "MPLS augmentation."; + container mpls { + config false; + description + "MPLS container, to be used as an augmentation target node + other MPLS sub-features state"; + } + } + } + + + +3. Security Considerations There are none. -3. IANA Considerations +4. IANA Considerations No new registrations for IANA. diff --git a/ietf/submit/test_submission.xml b/ietf/submit/test_submission.xml index 63cbcff1e..99197afd5 100644 --- a/ietf/submit/test_submission.xml +++ b/ietf/submit/test_submission.xml @@ -20,7 +20,7 @@ %(group)s - This document describes how to test tests. + This document describes how to test tests. @@ -28,17 +28,123 @@
- This document describes a protocol for testing tests. + This document describes a protocol for testing tests.
+
+
+ + file "ietf-mpls@2015-10-16.yang" + +module ietf-mpls { + + namespace "urn:ietf:params:xml:ns:yang:ietf-mpls"; + + prefix "mpls"; + + import ietf-routing { + prefix "rt"; + } + + import ietf-interfaces { + prefix "if"; + } + + organization "TBD"; + + contact "TBD"; + + description + "This YANG module defines the essential components for the + management of the MPLS subsystem."; + + revision "2015-10-16" { + description + "Initial revision"; + reference "RFC 3031: A YANG Data Model for base MPLS"; + } + + typedef mpls-label { + type uint32 { + range "0..1048575"; + } + description + "The MPLS label range"; + } + + typedef percent { + type uint16 { + range "0 .. 100"; + } + description "Percentage"; + } + + grouping interface-mpls { + description "MPLS interface properties grouping"; + leaf enabled { + type boolean; + description + "'true' if mpls encapsulation is enabled on the + interface. 'false' if mpls encapsulation is enabled + on the interface."; + } + } + + augment "/rt:routing/rt:routing-instance" { + description "MPLS augmentation."; + container mpls { + description + "MPLS container, to be used as an augmentation target node + other MPLS sub-features config, e.g. MPLS static LSP, MPLS + LDP LSPs, and Trafic Engineering MPLS LSP Tunnels, etc."; + + list interface { + key "name"; + description "List of MPLS interfaces"; + leaf name { + type if:interface-ref; + description + "The name of a configured MPLS interface"; + } + container config { + description "Holds intended configuration"; + uses interface-mpls; + } + container state { + config false; + description "Holds inuse configuration"; + uses interface-mpls; + } + } + } + } + + augment "/rt:routing-state/rt:routing-instance" { + description "MPLS augmentation."; + container mpls { + config false; + description + "MPLS container, to be used as an augmentation target node + other MPLS sub-features state"; + } + } +} + + + ]]> + +
+
+
- There are none. + There are none.
- No new registrations for IANA. + No new registrations for IANA.
diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 6ee022f16..310c11d69 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -1,7 +1,6 @@ import datetime import os import shutil -import re from django.conf import settings @@ -98,7 +97,7 @@ class SubmitTests(TestCase): self.assertTrue(os.path.exists(os.path.join(self.staging_dir, u"%s-%s.%s" % (name, rev, format)))) self.assertEqual(Submission.objects.filter(name=name).count(), 1) submission = Submission.objects.get(name=name) - self.assertTrue(re.search('\s+Summary:\s+0\s+errors|No nits found', submission.idnits_message)) + self.assertTrue(all([ c.passed!=False for c in submission.checks.all() ])) self.assertEqual(len(submission.authors_parsed()), 1) author = submission.authors_parsed()[0] self.assertEqual(author["name"], "Author Name") diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py index c3e291975..4562f780f 100644 --- a/ietf/submit/utils.py +++ b/ietf/submit/utils.py @@ -1,5 +1,4 @@ import os -import re import datetime from django.conf import settings @@ -18,23 +17,6 @@ from ietf.submit.mail import announce_to_lists, announce_new_version, announce_t from ietf.submit.models import Submission, SubmissionEvent, Preapproval, DraftSubmissionStateName from ietf.utils import unaccent from ietf.utils.log import log -from ietf.utils.pipe import pipe - -def check_idnits(path): - #p = subprocess.Popen([self.idnits, '--submitcheck', '--nitcount', path], stdout=subprocess.PIPE) - cmd = "%s --submitcheck --nitcount %s" % (settings.IDSUBMIT_IDNITS_BINARY, path) - code, out, err = pipe(cmd) - if code != 0: - log("idnits error: %s:\n Error %s: %s" %( cmd, code, err)) - return out - -def found_idnits(idnits_message): - if not idnits_message: - return False - success_re = re.compile('\s+Summary:\s+0\s+|No nits found') - if success_re.search(idnits_message): - return True - return False def validate_submission(submission): errors = {} diff --git a/ietf/submit/views.py b/ietf/submit/views.py index 842f094eb..d3f374afd 100644 --- a/ietf/submit/views.py +++ b/ietf/submit/views.py @@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse as urlreverse from django.core.validators import validate_email, ValidationError from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden from django.shortcuts import get_object_or_404, redirect, render +from django.utils.module_loading import import_string import debug # pyflakes:ignore @@ -17,9 +18,9 @@ from ietf.group.models import Group from ietf.ietfauth.utils import has_role, role_required from ietf.submit.forms import SubmissionUploadForm, NameEmailForm, EditSubmissionForm, PreapprovalForm, ReplacesForm from ietf.submit.mail import send_full_url, send_approval_request_to_group, send_submission_confirmation, send_manual_post_request -from ietf.submit.models import Submission, Preapproval, DraftSubmissionStateName +from ietf.submit.models import Submission, SubmissionCheck, Preapproval, DraftSubmissionStateName from ietf.submit.utils import approvable_submissions_for_user, preapprovals_for_user, recently_approved_by_user -from ietf.submit.utils import check_idnits, found_idnits, validate_submission, create_submission_event +from ietf.submit.utils import validate_submission, create_submission_event from ietf.submit.utils import post_submission, cancel_submission, rename_submission_files from ietf.utils.accesstoken import generate_random_key, generate_access_token from ietf.utils.draft import Draft @@ -92,9 +93,6 @@ def upload_submission(request): else: abstract = form.parsed_draft.get_abstract() - # check idnits - idnits_message = check_idnits(file_name['txt']) - # save submission try: submission = Submission.objects.create( @@ -114,12 +112,28 @@ def upload_submission(request): submission_date=datetime.date.today(), document_date=form.parsed_draft.get_creation_date(), replaces="", - idnits_message=idnits_message, ) except Exception as e: log("Exception: %s\n" % e) raise + # run submission checkers + def apply_check(submission, checker, method, fn): + func = getattr(checker, method) + passed, message, errors, warnings, items = func(fn) + check = SubmissionCheck(submission=submission, checker=checker.name, passed=passed, message=message, errors=errors, warnings=warnings, items=items) + check.save() + + for checker_path in settings.IDSUBMIT_CHECKER_CLASSES: + checker_class = import_string(checker_path) + checker = checker_class() + # ordered list of methods to try + for method in ("check_fragment_xml", "check_file_xml", "check_fragment_txt", "check_file_txt", ): + ext = method[-3:] + if hasattr(checker, method) and ext in file_name: + apply_check(submission, checker, method, file_name[ext]) + break + create_submission_event(request, submission, desc="Uploaded submission") return redirect("submit_submission_status_by_hash", submission_id=submission.pk, access_token=submission.access_token()) @@ -175,7 +189,7 @@ def submission_status(request, submission_id, access_token=None): raise Http404 errors = validate_submission(submission) - passes_idnits = found_idnits(submission.idnits_message) + passes_checks = all([ c.passed!=False for c in submission.checks.all() ]) is_secretariat = has_role(request.user, "Secretariat") is_chair = submission.group and submission.group.has_role(request.user, "chair") @@ -316,7 +330,7 @@ def submission_status(request, submission_id, access_token=None): 'selected': 'status', 'submission': submission, 'errors': errors, - 'passes_idnits': passes_idnits, + 'passes_checks': passes_checks, 'submitter_form': submitter_form, 'replaces_form': replaces_form, 'message': message, diff --git a/ietf/templates/bootstrap3/field_help_text_and_errors.html b/ietf/templates/bootstrap3/field_help_text_and_errors.html index f69d6d398..4683150aa 100644 --- a/ietf/templates/bootstrap3/field_help_text_and_errors.html +++ b/ietf/templates/bootstrap3/field_help_text_and_errors.html @@ -1 +1,3 @@ +
{% for item in help_text_and_errors %} {{ item }}
{% endfor %} +
diff --git a/ietf/templates/submit/submission_status.html b/ietf/templates/submit/submission_status.html index 4b17c87ce..c4d9e1812 100644 --- a/ietf/templates/submit/submission_status.html +++ b/ietf/templates/submit/submission_status.html @@ -37,32 +37,52 @@

Please fix errors in the form below.

{% endif %} -

I-D nits

+

Submission checks

- {% if passes_idnits %} - Your draft has been verified to meet I-D nits requirements. + {% if passes_checks %} + Your draft has been verified to pass the submission checks. {% else %} - Your draft has NOT been verified to meet I-D nits requirements. + Your draft has NOT been verified to pass the submission checks. {% endif %}

- + {% for check in submission.checks.all %} + {% if check.errors %} +

+ The {{check.checker}} returned {{ check.errors }} error{{ check.errors|pluralize }} + and {{ check.warnings }} warning -