diff --git a/src/VERSION.py b/src/VERSION.py index 3f2b401b..638a9b4d 100644 --- a/src/VERSION.py +++ b/src/VERSION.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -version = "Version 4.3.3 | 20200514" +version = "Version 4.4.0 beta | 20200528" diff --git a/src/proforma/api_v2.py b/src/proforma/api_v2.py index 23b7952a..faf05a59 100644 --- a/src/proforma/api_v2.py +++ b/src/proforma/api_v2.py @@ -35,10 +35,7 @@ import os import re -#import shutil import logging -#import xmlschema -#from requests.exceptions import InvalidSchema from . import task from . import grade import zipfile diff --git a/src/proforma/grade.py b/src/proforma/grade.py index daa67efc..512fe51b 100644 --- a/src/proforma/grade.py +++ b/src/proforma/grade.py @@ -32,8 +32,6 @@ from django.template.loader import render_to_string import os -#import codecs -#import re import logging diff --git a/src/proforma/task.py b/src/proforma/task.py index 9f6ffd36..b3abe553 100644 --- a/src/proforma/task.py +++ b/src/proforma/task.py @@ -27,22 +27,13 @@ from os.path import dirname -from xml.dom import minidom -from xml.dom.minidom import Node -from django.core.exceptions import ObjectDoesNotExist from django.core.files.uploadedfile import InMemoryUploadedFile -#from django.core.servers.basehttp import FileWrapper -from django.db import models -#from django.shortcuts import redirect -#from django.template import TemplateSyntaxError -#from django.template.loader import render_to_string from django.views.decorators.csrf import csrf_exempt from django.core.files import File from django.http import HttpResponse -from lxml import etree from lxml import objectify import logging import zipfile @@ -51,12 +42,7 @@ import hashlib from tasks.models import Task -#from attestation.models import Rating from checker.checker import CreateFileChecker -#from checker.models import Checker -#from solutions.models import Solution, SolutionFile -#from tasks.models import Task, MediaFile -#from . import task_v0_94 from . import task_v1_01 from . import task_v2_00 @@ -344,6 +330,7 @@ def import_task_internal(filename, task_file): format_namespace_v0_9_4 = "urn:proforma:task:v0.9.4" format_namespace_v1_0_1 = "urn:proforma:task:v1.0.1" format_namespace_v2_0 = "urn:proforma:v2.0" + format_namespace_v2_0_1 = "urn:proforma:v2.0.1" # rxcoding = re.compile(r"encoding=\"(?P[\w.-]+)") @@ -386,7 +373,11 @@ def import_task_internal(filename, task_file): response_data = task_v1_01.import_task(task_xml, xml_object, dict_zip_files) elif format_namespace_v2_0 in list(xml_object.nsmap.values()): logger.debug('handle 2.0 task') - task_2 = task_v2_00.Task_2_00(task_xml, xml_object, hash, dict_zip_files) + task_2 = task_v2_00.Task_2_00(task_xml, xml_object, hash, dict_zip_files, format_namespace_v2_0) + response_data = task_2.import_task() + elif format_namespace_v2_0_1 in list(xml_object.nsmap.values()): + logger.debug('handle 2.0.1 task') + task_2 = task_v2_00.Task_2_00(task_xml, xml_object, hash, dict_zip_files, format_namespace_v2_0_1) response_data = task_2.import_task() else: raise Exception("The Exercise could not be imported!\r\nOnly support for the following namespaces: " + diff --git a/src/proforma/task_v0_94.py b/src/proforma/task_v0_94.py index 078030a6..03d1b2a2 100644 --- a/src/proforma/task_v0_94.py +++ b/src/proforma/task_v0_94.py @@ -21,10 +21,8 @@ # functions for importing ProFormA tasks version 0.9.4 into Praktomat database. # Version 0.9.4 is depricated, do not use anymore!! -from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse from django.core.files import File -from django.views.decorators.csrf import csrf_exempt from datetime import datetime from lxml import objectify @@ -38,7 +36,7 @@ from solutions.models import Solution, SolutionFile from checker.checker import CreateFileChecker, CheckStyleChecker, JUnitChecker, AnonymityChecker, \ DejaGnu, TextNotChecker, PythonChecker, RemoteSQLChecker, TextChecker, SetlXChecker -from checker.builder import JavaBuilder +from checker.compiler import JavaBuilder from . import task diff --git a/src/proforma/task_v1_01.py b/src/proforma/task_v1_01.py index 4ad85638..5a139f79 100644 --- a/src/proforma/task_v1_01.py +++ b/src/proforma/task_v1_01.py @@ -24,7 +24,6 @@ # TASK VERSION 1.0.1 IS NO LONGER SUPPORTED -from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse from django.core.files import File @@ -33,14 +32,12 @@ import re import tempfile import logging -from os.path import dirname from tasks.models import Task from accounts.models import User from solutions.models import Solution, SolutionFile from checker.checker import CreateFileChecker, CheckStyleChecker, JUnitChecker, AnonymityChecker, \ PythonChecker, SetlXChecker -#from checker.checker import TextNotChecker, TextChecker, DejaGnu from checker.compiler import JavaBuilder, CBuilder diff --git a/src/proforma/task_v2_00.py b/src/proforma/task_v2_00.py index aa175c61..47c0b21b 100644 --- a/src/proforma/task_v2_00.py +++ b/src/proforma/task_v2_00.py @@ -24,16 +24,11 @@ import os import tempfile from datetime import datetime -from operator import getitem import xmlschema -#from django.views.decorators.csrf import csrf_exempt from django.core.files import File -#from lxml import objectify - -#from accounts.models import User from checker.checker import PythonChecker, SetlXChecker from checker.checker import CheckStyleChecker, JUnitChecker, \ CreateFileChecker @@ -44,12 +39,13 @@ from django.conf import settings import logging -from functools import reduce + logger = logging.getLogger(__name__) BASE_DIR = os.path.dirname(os.path.dirname(__file__)) PARENT_BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -XSD_V_2_PATH = "xsd/proforma_v2.0.xsd" +XSD_V_2_PATH = "proforma/xsd/proforma_v2.0.xsd" +XSD_V_2_01_PATH = "proforma/xsd/proforma_2.0.1_rc.xsd" SYSUSER = "sys_prod" CACHE_TASKS = True @@ -103,10 +99,6 @@ def _getTask(self): return self._task object = property(_getTask) - def _getitem_from_dict(self, dataDict, mapList): - """Iterate nested dictionary""" - return reduce(getitem, mapList, dataDict) - def delete(self): self._task.delete() self._task = None @@ -140,13 +132,13 @@ def _read_basic_attributes(self, xml_dict): def _read_submission_restriction(self, xml_dict): # todo add file restrictions - path = ['submission-restrictions'] max_size = None - restriction = self._getitem_from_dict(xml_dict, path) + restriction = xml_dict.get('p:submission-restrictions') try: max_size = restriction.get("@max-size") except AttributeError: + logger.error('could not find max-size, set to default') # no max size given => use default (1MB) max_size = 1000000 @@ -230,11 +222,8 @@ def save(self): class Task_2_00: - _format_namespace = "urn:proforma:v2.0" - _ns = {"p": _format_namespace} - # constructor - def __init__(self, task_xml, xml_obj, hash, dict_zip_files=None): + def __init__(self, task_xml, xml_obj, hash, dict_zip_files, format_namespace): self._task_xml = task_xml self._xml_obj = xml_obj self._hash = hash @@ -242,10 +231,12 @@ def __init__(self, task_xml, xml_obj, hash, dict_zip_files=None): self._praktomat_task = None self._val_order = 1 self._praktomat_files = None + self._format_namespace = format_namespace + self._ns = {"p": self._format_namespace} # read all files from task and put them into a dictionary def _create_praktomat_files(self, xml_obj, external_file_dict=None, ): - namespace = Task_2_00._ns + namespace = self._ns orphain_files = dict() @@ -265,12 +256,14 @@ def _create_praktomat_files(self, xml_obj, external_file_dict=None, ): orphain_files[k.attrib.get("id")] = my_temp elif k.xpath("p:attached-bin-file", namespaces=namespace): filename = k['attached-bin-file'].text - if external_file_dict is None: - raise Exception('no files in zip found') + logger.debug('attached task file: ' + k.attrib.get("id") + ' => ' + filename) + orphain_files[k.attrib.get("id")] = external_file_dict[filename] + elif k.xpath("p:attached-txt-file", namespaces=namespace): + filename = k['attached-txt-file'].text logger.debug('attached task file: ' + k.attrib.get("id") + ' => ' + filename) orphain_files[k.attrib.get("id")] = external_file_dict[filename] else: - raise Exception('unsupported file type in task.xml (embedded-bin-file or attached-txt-file)') + raise task.TaskXmlException('embedded-bin-file is not supported') # List with all files that are referenced by tests list_of_test_files = xml_obj.xpath("/p:task/p:tests/p:test/p:test-configuration/" @@ -297,13 +290,13 @@ def _create_java_compilertest(self, xmlTest): _file_pattern=r"^.*\.[jJ][aA][vV][aA]$", _main_required=False ) - x = Praktomat_Test_2_00(inst, Task_2_00._ns) + x = Praktomat_Test_2_00(inst, self._ns) x.set_test_base_parameters(xmlTest) x.save() def _create_java_unit_test(self, xmlTest): - checker_ns = Task_2_00._ns.copy() + checker_ns = self._ns.copy() checker_ns['unit_new'] = 'urn:proforma:tests:unittest:v1.1' checker_ns['unit'] = 'urn:proforma:tests:unittest:v1' @@ -339,13 +332,13 @@ def _create_java_unit_test(self, xmlTest): # todo create: something like TaskException class raise Exception("Junit-Version is not supported: " + str(junit_version)) - x = Praktomat_Test_2_00(inst, Task_2_00._ns) + x = Praktomat_Test_2_00(inst, self._ns) x.set_test_base_parameters(xmlTest) self._val_order = x.add_files_to_test(xmlTest, self. _praktomat_files, self._val_order, None) x.save() def _create_java_checkstyle_test(self, xmlTest): - checker_ns = Task_2_00._ns.copy() + checker_ns = self._ns.copy() checker_ns['check'] = 'urn:proforma:tests:java-checkstyle:v1.1' inst = CheckStyleChecker.CheckStyleChecker.objects.create(task=self._praktomat_task.object, order=self._val_order) @@ -372,7 +365,7 @@ def _create_java_checkstyle_test(self, xmlTest): "check:java-checkstyle/" "check:max-checkstyle-warnings", namespaces=checker_ns)[0] - x = Praktomat_Test_2_00(inst, Task_2_00._ns) + x = Praktomat_Test_2_00(inst, self._ns) x.set_test_base_parameters(xmlTest) def set_mainfile(inst, value): inst.configuration = value @@ -383,7 +376,7 @@ def set_mainfile(inst, value): def _create_setlx_test(self, xmlTest): inst = SetlXChecker.SetlXChecker.objects.create(task=self._praktomat_task.object, order=self._val_order) - x = Praktomat_Test_2_00(inst, Task_2_00._ns) + x = Praktomat_Test_2_00(inst, self._ns) x.set_test_base_parameters(xmlTest) def set_mainfile(inst, value): inst.testFile = value @@ -393,20 +386,26 @@ def set_mainfile(inst, value): def _create_python_test(self, xmlTest): inst = PythonChecker.PythonChecker.objects.create(task=self._praktomat_task.object, order=self._val_order) - x = Praktomat_Test_2_00(inst, Task_2_00._ns) + x = Praktomat_Test_2_00(inst, self._ns) x.set_test_base_parameters(xmlTest) def set_mainfile(inst, value): inst.doctest = value self._val_order = x.add_files_to_test(xmlTest, self. _praktomat_files, self._val_order, firstHandler=set_mainfile) x.save() + def get_xsd_path(self): + if self._format_namespace == 'urn:proforma:v2.0': + return XSD_V_2_PATH + if self._format_namespace == 'urn:proforma:v2.0.1': + return XSD_V_2_01_PATH + raise Exception('ProForma XSD not found for namespace ' + self._format_namespace) def import_task(self): - task_in_xml = self._xml_obj.xpath("/p:task", namespaces=Task_2_00._ns) + task_in_xml = self._xml_obj.xpath("/p:task", namespaces=self._ns) task_uuid = task_in_xml[0].attrib.get("uuid") logger.debug('uuid is ' + task_uuid) - task_title = self._xml_obj.xpath("/p:task/p:title", namespaces=Task_2_00._ns)[0] + task_title = self._xml_obj.xpath("/p:task/p:title", namespaces=self._ns)[0] logger.debug('title is "' + task_title + '"') # check if task is already in database @@ -417,7 +416,7 @@ def import_task(self): # no need to actually validate xml against xsd # (it is only time consuming) - schema = xmlschema.XMLSchema(os.path.join(PARENT_BASE_DIR, XSD_V_2_PATH)) + schema = xmlschema.XMLSchema(os.path.join(PARENT_BASE_DIR, self.get_xsd_path())) # todo: remove because it is very expensive (bom, about 350ms) # logger.debug('task_xml = ' + str(task_xml)) t = tempfile.NamedTemporaryFile(delete=True) @@ -426,6 +425,8 @@ def import_task(self): t.seek(0) xml_dict = schema.to_dict(t) + if xml_dict == None: + raise Exception('cannot create dictionary from task') # xml_dict = validate_xml(xml=task_xml) @@ -440,7 +441,7 @@ def import_task(self): self._create_praktomat_files(xml_obj=self._xml_obj, external_file_dict=self._dict_zip_files) # create test objects for xmlTest in self._xml_obj.tests.iterchildren(): - testtype = xmlTest.xpath("p:test-type", namespaces=Task_2_00._ns)[0].text + testtype = xmlTest.xpath("p:test-type", namespaces=self._ns)[0].text if testtype == "java-compilation": # todo check compilation_xsd logger.debug('** create_java_compilertest') self._create_java_compilertest(xmlTest) diff --git a/src/xsd/praktomat_2.0.xsd b/src/proforma/xsd/praktomat_2.0.xsd similarity index 100% rename from src/xsd/praktomat_2.0.xsd rename to src/proforma/xsd/praktomat_2.0.xsd diff --git a/src/proforma/xsd/proforma_2.0.1_rc.xsd b/src/proforma/xsd/proforma_2.0.1_rc.xsd new file mode 100644 index 00000000..a0916fb3 --- /dev/null +++ b/src/proforma/xsd/proforma_2.0.1_rc.xsd @@ -0,0 +1,1062 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Root element type of a ProFormA grading-hints element. This includes the complete + hierarchical grading scheme with all tests references, weights, accumulating functions and nullify conditions. + Hierarchy nodes and conditions can get a title and descriptions. All information below this element except the + root node is optional. Grader-specific hints from other XML namespaces can be included in xs:any elements. + + + + + + Root node of the grading scheme hierarchy. If no children are specified, the + total grading score will be obtained by including all test results scores. The "function" attribute + specifies the accumulator function. + + + + + + Inner node of the grading scheme hierarchy. This can be a child of the root + node or any descendant node. A "combine" node specifies how to condense several sub results. Sub results can + be test results or again "combined" results. + + + + + + Any non-standard information that can be used by a grader or humans to + calculate a total result from tests results. + + + + + + + + + Inner node of the grading scheme hierarchy. There are only two types of inner + nodes: the "root" node and "combine" nodes. + + + + + + A not too long title to be shown above the grading result represented by this + node. + + + + + + A more or less comprehensive description of the problem aspect represented by + this node. + + + + + + A more or less comprehensive internal description of the problem aspect + represented by this node. Internal descriptions are meant for teachers and maybe grading assistants. + Internal descriptions are not shown to students. The internal description could include didactic background + information and possibly technical or organizational details about this grading aspect. + + + + + + + + + + + Node identifier. It is optional for the "root" node and required for "combine" + nodes. + + + + + + Accumulator function that is used to condense several sub results to a single + result. + + + + + + + Specifies the minimum of several sub scores. This can be used in an "all + or nothing" situation, where a parent score should reflect the worst of the child results. Weights can + be attached to children to express the valency of a child's result. The child node representing the + easiest aspect among its siblings could get the weight 1. Child nodes for grading aspects connected with + a higher effort represent scores that are more difficult to achieve. These child nodes could get weights + larger than 1. This would guarantee, that when all child nodes results are in [0,1] also the parent node + result is in [0,1]. + + + + + + Specifies the maximum of several sub scores. This is used in an "one + success is enough" situation, where a parent score should reflect the best of the child results. An + example is a task or a graded problem aspect that could be solved in different ways and for each way + there is a separate test element in the task. A solution that succeeds any one of these tests is + regarded successful. If one of the different ways of solving the task is more sophisticated than the + others, the respective child test could get the highest weight 1. Easier, less valent solution paths get + lower weights between 0 and 1. This would guarantee, that when all child nodes results are in [0,1] also + the parent node result is in [0,1]. + + + + + + Specifies the sum of several sub scores. This is used in a situation, + where every child represents a problem aspect that could be solved more or less independently of the + other aspects. Weights can be attached to child nodes. Those child nodes representing easy problem + aspects could get lower weights than other aspects. If all weights of all direct children of a node add + up to 1, this would guarantee, that the parent node result is in [0,1] when all child nodes results are + in [0,1]. + + + + + + + + + + + Inner nodes of the grading scheme hierarchy carry pointers to children. This + element represents such a pointer. There are two kinds of pointers: "test-ref" pointers and "combine-ref" + pointers. + + + + + + + Specifies a composite condition when the sub result of the pointed-at node + should get nullified. The pointed-at node is a test or a combine node. If the composite condition + evaluates to False, it has no effect. Otherwise, the score of the pointed-at node is not accumulated into + the condensed result of the pointing-from-node. State differently, the score that flows into the + accumulator function of the pointing-from-node, is assumed to be emitted as 0 from the pointed-at node. + + + + + + Specifies a _comparison_ condition when the sub result of the pointed-at + node should get nullified. The only difference to <nullify-conditions> is the trailing "s" and the + fact, that <nullify-conditions> represents a composite condition while <nullify-condition> + represents a simple comparison condition. + + + + + + + + Specifies a weight that is multiplied to the sub result of the pointed-at node + when flowing into the accumulator function. The pointed-at node is a test or a combine node. When calculating + the condensed result for the pointing-from node, the score of the pointed-at node is multiplied by the weight, + if present. Otherwise nothing is multiplied. + + + + + + + + A "test-ref" node points to a test in a ProFormA task. As such the result of the + pointed at test is obtained and included in a bottom-up fashion in the calculation of the total result. + + + + + + + + A not too long title to be shown above the pointed-at test's result. + Overrides the title of the pointed-at test element. This can be used especially when pointing to sub + test results. + + + + + + A more or less comprehensive description of the problem aspect represented + by the pointed-at test. This can be used especially when pointing to sub test results. + + + + + + A more or less comprehensive internal description of the problem aspect + represented by the pointed-at test. This can be used especially when pointing to sub test results. + Internal descriptions are meant for teachers and maybe grading assistants. Internal descriptions are not + shown to students. The internal description could include didactic background information and possibly + technical or organizational details about this grading aspect. + + + + + + + The id of the pointed-at test. + + + + + If the pointed at test exhibits sub test results, this points to one of the + sub results. Examples are individual test cases in a unit test specification, individual violation rules + in a static code analyzer, individual error classes in a compilation step, etc. Since the sub-ref format + or content is test-tool-specific, it is not normed in the ProFormA format. + + + + + + + + + + A "combine-ref" node points to a "combine" node in the grading scheme hierarchy. + As such the result of the pointed at node is obtained and included in a bottom-up fashion in the calculation of + the total result. + + + + + + + The id of the pointed-at combine node. + + + + + + + + + Specifies an operand of a composite nullify condition. + + + + + A title to be displayed when explaining a score nullification to students or + teachers. Front ends might decide to explain nullification conditions in a grading scheme even if for a + certain submission no nullification took place. + + + + + + A detailed description to be displayed when explaining a score nullification. + Front ends might decide to extend this description by an automatically generated, human readable version of + the involved boolean or comparison expression. + + + + + + A detailed description to be displayed to teachers and grading assistants + only. + + + + + + + + + Specifies a composite condition when the sub result of a pointed-at node should + get nullified. The composite condition is attributed with one of the boolean operators { and, or }. Further it + contains operands that usually are of the nullify-condition type, which represents a simple comparison. + Nevertheless a composite condition can have nested composite conditions as operands as well. + + + + + + + + + An operand of the boolean expression, itself being a composite + condition. + + + + + + A simple comparison condition as an operand of the boolean expression. + + + + + + + + The boolean operator of the boolean expression. + + + + + + + + + + + + + + + Specifies a simple comparison condition when the sub result of a pointed-at node + should get nullified. This simple comparison condition is attributed with one of the six common comparison + operators. Further it contains operands that refer to tests or combine nodes or that specify a numerical + constant, which a result should be compared to. + + + + + + + + + An operand of the comparison expression pointing to a "combine" node. + When evaluating the condition, the numerical score calculated for the referenced combine node will be + used as an operand in comparison. + + + + + + An operand of the comparison expression pointing to a "test". When + evaluating the condition, the numerical score delivered by the referenced test will be used as an + operand in comparison. + + + + + + A numerical constant serving as an operand of the comparison expression. + The constant is specified as a "value" attribute in the nullify-literal element. + + + + + + + + The comparison operator of the comparison expression. + + + + + + equals + + + + + not equals + + + + + greater than + + + + + greater than or equals + + + + + less than + + + + + less than or equals + + + + + + + + + + + + Specifies an operand of a comparison nullify condition. + + + + + + An operand of a comparison expression pointing to a "combine" node. + + + + + + + The id of the pointed-at combine node. + + + + + + + + + An operand of a comparison expression pointing to a "test". + + + + + + The id of the pointed-at test. + + + + + If the pointed at test exhibits sub test results, this points to one of the + sub results. Examples are individual test cases in a unit test specification, individual violation rules + in a static code analyzer, individual error classes in a compilation step, etc. Since the sub-ref format + or content is test-tool-specific, it is not normed in the ProFormA format. + + + + + + + + + + A numerical constant serving as an operand of the comparison expression. + + + + + + + A numerical constant value to be compared with. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A list of existing programming languages is + available in the white paper appendix. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A list of existing test types and recommendations for future ones is + available in the white paper appendix. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/xsd/proforma_v2.0.xsd b/src/proforma/xsd/proforma_v2.0.xsd similarity index 100% rename from src/xsd/proforma_v2.0.xsd rename to src/proforma/xsd/proforma_v2.0.xsd