From 636b03ddbd5d5ccb92dcf1d48f6314776b0cce27 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Tue, 17 Oct 2023 10:14:12 +0200 Subject: [PATCH 01/12] [frontend/task_problems] Adding line offset for CodeMirror code blocks A teacher could need to change the code lines offset in order to send back correct review when parsing student code into a template. Problem : changing the first_line parameter in the template_helper.render() function is effective only after restarting the server. --- inginious/frontend/app.py | 1 + inginious/frontend/static/js/common.js | 5 +++-- inginious/frontend/task_problems.py | 2 +- inginious/frontend/templates/tasks/code.html | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/inginious/frontend/app.py b/inginious/frontend/app.py index 5f4e8c522b..f5f38236e0 100644 --- a/inginious/frontend/app.py +++ b/inginious/frontend/app.py @@ -302,6 +302,7 @@ def flask_internalerror(e): flask_app.privacy_page = config.get("privacy_page", None) flask_app.static_directory = config.get("static_directory", "./static") flask_app.webdav_host = config.get("webdav_host", None) + flask_app.jinja_env.auto_reload = True # Init the mapping of the app init_flask_mapping(flask_app) diff --git a/inginious/frontend/static/js/common.js b/inginious/frontend/static/js/common.js index 0382ffe754..f6efe38d93 100644 --- a/inginious/frontend/static/js/common.js +++ b/inginious/frontend/static/js/common.js @@ -10,7 +10,7 @@ function init_common() colorizeStaticCode(); $('.code-editor').each(function(index, elem) { - registerCodeEditor(elem, $(elem).attr('data-x-language'), $(elem).attr('data-x-lines')); + registerCodeEditor(elem, $(elem).attr('data-x-language'), $(elem).attr('data-x-lines'), $(elem).attr('data-x-first-line')); }); //Fix a bug with codemirror and bootstrap tabs @@ -69,7 +69,7 @@ function colorizeStaticCode() } //Register and init a code editor (ace) -function registerCodeEditor(textarea, lang, lines) +function registerCodeEditor(textarea, lang, lines, firstline) { var mode = CodeMirror.findModeByName(lang); if(mode == undefined) @@ -79,6 +79,7 @@ function registerCodeEditor(textarea, lang, lines) var editor = CodeMirror.fromTextArea(textarea, { lineNumbers: true, + firstLineNumber: parseInt(firstline), mode: mode["mime"], foldGutter: true, styleActiveLine: true, diff --git a/inginious/frontend/task_problems.py b/inginious/frontend/task_problems.py index 7c62eef397..fcb2bed093 100644 --- a/inginious/frontend/task_problems.py +++ b/inginious/frontend/task_problems.py @@ -82,7 +82,7 @@ def show_input(self, template_helper, language, seed): header = ParsableText(self.gettext(language,self._header), "rst", translation=self.get_translation_obj(language)) return template_helper.render("tasks/code.html", inputId=self.get_id(), header=header, - lines=8, maxChars=0, language=self._language, optional=self._optional, + lines=8, first_line=1, maxChars=0, language=self._language, optional=self._optional, default=self._default) @classmethod diff --git a/inginious/frontend/templates/tasks/code.html b/inginious/frontend/templates/tasks/code.html index 8fef8f3a7c..0dda84a578 100644 --- a/inginious/frontend/templates/tasks/code.html +++ b/inginious/frontend/templates/tasks/code.html @@ -9,4 +9,5 @@ class="code-editor form-control {% if '/' in inputId %} single {% endif %}" data-x-language="{{language}}" data-x-lines="{{lines}}" + data-x-first-line="{{first_line}}" data-optional="{% if optional %}{{True}}{% else %}{{False}}{% endif %}">{{ default }} From 17c56c29752845ec51e21d38f96f2d55fcbfc91f Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:11:48 +0200 Subject: [PATCH 02/12] [frontend/task_problems] Displaying no line offset when first_line parameter not defined --- inginious/frontend/static/js/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inginious/frontend/static/js/common.js b/inginious/frontend/static/js/common.js index f6efe38d93..d00f6939b9 100644 --- a/inginious/frontend/static/js/common.js +++ b/inginious/frontend/static/js/common.js @@ -10,7 +10,7 @@ function init_common() colorizeStaticCode(); $('.code-editor').each(function(index, elem) { - registerCodeEditor(elem, $(elem).attr('data-x-language'), $(elem).attr('data-x-lines'), $(elem).attr('data-x-first-line')); + registerCodeEditor(elem, $(elem).attr('data-x-language'), $(elem).attr('data-x-lines'), $(elem).attr('data-x-first-line') !== undefined ? $(elem).attr('data-x-first-line') : "1"); }); //Fix a bug with codemirror and bootstrap tabs From d1542aa6f372e6bac4032a5647dd2818d44cf741 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:14:49 +0200 Subject: [PATCH 03/12] [frontend/common] Fixing firstLineNumber param for CodeMirror Was checking the value of data-x-first-line only for textareas with code-editor class. Now doing it in registerCodeEditor() function for every case. --- inginious/frontend/app.py | 1 - inginious/frontend/static/js/common.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/inginious/frontend/app.py b/inginious/frontend/app.py index f5f38236e0..5f4e8c522b 100644 --- a/inginious/frontend/app.py +++ b/inginious/frontend/app.py @@ -302,7 +302,6 @@ def flask_internalerror(e): flask_app.privacy_page = config.get("privacy_page", None) flask_app.static_directory = config.get("static_directory", "./static") flask_app.webdav_host = config.get("webdav_host", None) - flask_app.jinja_env.auto_reload = True # Init the mapping of the app init_flask_mapping(flask_app) diff --git a/inginious/frontend/static/js/common.js b/inginious/frontend/static/js/common.js index d00f6939b9..b2088995ab 100644 --- a/inginious/frontend/static/js/common.js +++ b/inginious/frontend/static/js/common.js @@ -10,7 +10,7 @@ function init_common() colorizeStaticCode(); $('.code-editor').each(function(index, elem) { - registerCodeEditor(elem, $(elem).attr('data-x-language'), $(elem).attr('data-x-lines'), $(elem).attr('data-x-first-line') !== undefined ? $(elem).attr('data-x-first-line') : "1"); + registerCodeEditor(elem, $(elem).attr('data-x-language'), $(elem).attr('data-x-lines'), $(elem).attr('data-x-first-line')); }); //Fix a bug with codemirror and bootstrap tabs @@ -76,6 +76,8 @@ function registerCodeEditor(textarea, lang, lines, firstline) mode = {"mode": "plain", "mime": "text/plain"}; var is_single = $(textarea).hasClass('single'); + // if firstline not given, set to "1" + var firstline = firstline ?? "1"; var editor = CodeMirror.fromTextArea(textarea, { lineNumbers: true, From 57d6af4496dbe86f9e26b698211045362e2905e9 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:22:37 +0200 Subject: [PATCH 04/12] Adding form field for line offset in subproblem edit --- .../templates/course_admin/subproblems/code.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/inginious/frontend/templates/course_admin/subproblems/code.html b/inginious/frontend/templates/course_admin/subproblems/code.html index 49228f4d74..0b18dc9bb3 100644 --- a/inginious/frontend/templates/course_admin/subproblems/code.html +++ b/inginious/frontend/templates/course_admin/subproblems/code.html @@ -47,3 +47,12 @@ {% endif %} + +{% if multiline %} +
+ +
+ +
+
+{% endif %} From f6958428ec249c06e160a9445c7cc30bd4f36707 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:50:26 +0200 Subject: [PATCH 05/12] Retrieving line offset from subproblem form, checking it's value at submit and apply it to the problem's code block --- inginious/frontend/pages/course_admin/task_edit.py | 14 ++++++++++++++ inginious/frontend/static/js/studio.js | 2 ++ inginious/frontend/task_problems.py | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/inginious/frontend/pages/course_admin/task_edit.py b/inginious/frontend/pages/course_admin/task_edit.py index f68da25e6a..c4eaa8ff82 100644 --- a/inginious/frontend/pages/course_admin/task_edit.py +++ b/inginious/frontend/pages/course_admin/task_edit.py @@ -89,6 +89,7 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ __, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) data = flask.request.form.copy() data["task_file"] = flask.request.files.get("task_file") + print(data) # Else, parse content try: @@ -142,6 +143,19 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ # Network grading data["network_grading"] = "network_grading" in data + + # Checking problem inputs + print(problems) + for problem, values in problems.items(): + try: + offset = int(values["offset"]) + if offset < 1: + return json.dumps( + {"status": "error", "message": _("The line offset for a problem must be positive!")}) + except ValueError : + return json.dumps( + {"status": "error", "message": _("The line offset for a problem must be an integer!")}) + except Exception as message: return json.dumps({"status": "error", "message": _("Your browser returned an invalid form ({})").format(message)}) diff --git a/inginious/frontend/static/js/studio.js b/inginious/frontend/static/js/studio.js index d309f8dcab..40bf86fb7c 100644 --- a/inginious/frontend/static/js/studio.js +++ b/inginious/frontend/static/js/studio.js @@ -411,6 +411,8 @@ function studio_init_template_code(well, pid, problem) var default_editor = registerCodeEditor(default_tag, 'text', default_tag.tagName === "INPUT" ? 1 : 10); if("default" in problem) default_editor.setValue(problem["default"]); + if("offset" in problem) + $('#offset-' + pid, well).val(problem["offset"]); } /** diff --git a/inginious/frontend/task_problems.py b/inginious/frontend/task_problems.py index fcb2bed093..5aaec80e43 100644 --- a/inginious/frontend/task_problems.py +++ b/inginious/frontend/task_problems.py @@ -69,6 +69,7 @@ class DisplayableCodeProblem(CodeProblem, DisplayableProblem): def __init__(self, problemid, content, translations, taskfs): super(DisplayableCodeProblem, self).__init__(problemid, content, translations, taskfs) + self._first_line = content.get("offset", 1) @classmethod def get_type_name(cls, language): @@ -82,7 +83,7 @@ def show_input(self, template_helper, language, seed): header = ParsableText(self.gettext(language,self._header), "rst", translation=self.get_translation_obj(language)) return template_helper.render("tasks/code.html", inputId=self.get_id(), header=header, - lines=8, first_line=1, maxChars=0, language=self._language, optional=self._optional, + lines=8, first_line=self._first_line, maxChars=0, language=self._language, optional=self._optional, default=self._default) @classmethod From 26fcaf93a0d88abc6bffca08292132c686ecec74 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:51:50 +0200 Subject: [PATCH 06/12] [static/common] fixing first line transformation to int for CodeMirror Was changing the firstline value into a integer or set it to 1 if it was not given. Was oing it outside the registerCodeEditor() function. But as it is used in other places this action wasn't performed each time. Doing this transformation inside the function now. --- inginious/frontend/static/js/common.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/inginious/frontend/static/js/common.js b/inginious/frontend/static/js/common.js index b2088995ab..44a66975b2 100644 --- a/inginious/frontend/static/js/common.js +++ b/inginious/frontend/static/js/common.js @@ -76,12 +76,16 @@ function registerCodeEditor(textarea, lang, lines, firstline) mode = {"mode": "plain", "mime": "text/plain"}; var is_single = $(textarea).hasClass('single'); - // if firstline not given, set to "1" - var firstline = firstline ?? "1"; + // if firstline null or undefined, set to "1" + firstline = parseInt(firstline)?? "1"; + if (isNaN(firstline)) + firstline = 1; + + var editor = CodeMirror.fromTextArea(textarea, { lineNumbers: true, - firstLineNumber: parseInt(firstline), + firstLineNumber: firstline, mode: mode["mime"], foldGutter: true, styleActiveLine: true, From f36d81906d394b1b3976ccb408dfa47257c05c02 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:06:38 +0200 Subject: [PATCH 07/12] [pages/task_edit] Allowing the line offset field to be empty When the line offset was empty it was retrieved as an empty string. It couldn't be transformed into an integer so an error message appeared. Fixed that. --- .../frontend/pages/course_admin/task_edit.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/inginious/frontend/pages/course_admin/task_edit.py b/inginious/frontend/pages/course_admin/task_edit.py index c4eaa8ff82..d96bfc44ae 100644 --- a/inginious/frontend/pages/course_admin/task_edit.py +++ b/inginious/frontend/pages/course_admin/task_edit.py @@ -145,16 +145,16 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ data["network_grading"] = "network_grading" in data # Checking problem inputs - print(problems) for problem, values in problems.items(): - try: - offset = int(values["offset"]) - if offset < 1: + if len(values["offset"]) != 0: + try: + offset = int(values["offset"]) + if offset < 1: + return json.dumps( + {"status": "error", "message": _("The line offset for a problem must be positive!")}) + except ValueError : return json.dumps( - {"status": "error", "message": _("The line offset for a problem must be positive!")}) - except ValueError : - return json.dumps( - {"status": "error", "message": _("The line offset for a problem must be an integer!")}) + {"status": "error", "message": _("The line offset for a problem must be an integer!")}) except Exception as message: return json.dumps({"status": "error", "message": _("Your browser returned an invalid form ({})").format(message)}) From 59596f83164cea117e89461009c0a8996d98d75e Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:03:22 +0200 Subject: [PATCH 08/12] [Fronted/task_edit] Raising error in CodeProblem.parse_problem() instead of task_edit.py --- inginious/common/tasks_problems.py | 8 ++++++++ inginious/frontend/pages/course_admin/task_edit.py | 12 ------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/inginious/common/tasks_problems.py b/inginious/common/tasks_problems.py index ad451f2217..8a4d18ba29 100644 --- a/inginious/common/tasks_problems.py +++ b/inginious/common/tasks_problems.py @@ -181,6 +181,14 @@ def input_is_consistent(self, task_input, default_allowed_extension, default_max @classmethod def parse_problem(self, problem_content): + # Checking problem edit inputs + if len(problem_content["offset"]) != 0: + try: + offset = int(problem_content["offset"]) + if offset < 1: + raise Exception("Line offset must be positive!") + except ValueError: + raise Exception("Line offset must be an integer!") return Problem.parse_problem(problem_content) @classmethod diff --git a/inginious/frontend/pages/course_admin/task_edit.py b/inginious/frontend/pages/course_admin/task_edit.py index d96bfc44ae..032b78a900 100644 --- a/inginious/frontend/pages/course_admin/task_edit.py +++ b/inginious/frontend/pages/course_admin/task_edit.py @@ -89,7 +89,6 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ __, __ = self.get_course_and_check_rights(courseid, allow_all_staff=False) data = flask.request.form.copy() data["task_file"] = flask.request.files.get("task_file") - print(data) # Else, parse content try: @@ -144,17 +143,6 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ # Network grading data["network_grading"] = "network_grading" in data - # Checking problem inputs - for problem, values in problems.items(): - if len(values["offset"]) != 0: - try: - offset = int(values["offset"]) - if offset < 1: - return json.dumps( - {"status": "error", "message": _("The line offset for a problem must be positive!")}) - except ValueError : - return json.dumps( - {"status": "error", "message": _("The line offset for a problem must be an integer!")}) except Exception as message: return json.dumps({"status": "error", "message": _("Your browser returned an invalid form ({})").format(message)}) From 692ab98a0b9a69e26f9cb4d954cd574a5a5a88c1 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:06:14 +0200 Subject: [PATCH 09/12] [static/common] Replacing parameter check in registerCodeEditor() with default parameter --- inginious/frontend/static/js/common.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/inginious/frontend/static/js/common.js b/inginious/frontend/static/js/common.js index 44a66975b2..c88b745827 100644 --- a/inginious/frontend/static/js/common.js +++ b/inginious/frontend/static/js/common.js @@ -69,17 +69,13 @@ function colorizeStaticCode() } //Register and init a code editor (ace) -function registerCodeEditor(textarea, lang, lines, firstline) +function registerCodeEditor(textarea, lang, lines, firstline=1) { var mode = CodeMirror.findModeByName(lang); if(mode == undefined) mode = {"mode": "plain", "mime": "text/plain"}; var is_single = $(textarea).hasClass('single'); - // if firstline null or undefined, set to "1" - firstline = parseInt(firstline)?? "1"; - if (isNaN(firstline)) - firstline = 1; From 8ed921dec1c84d5e10632e0eb9e7cca64f579a44 Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:50:36 +0200 Subject: [PATCH 10/12] [common/tasks_problems] storing offset as integer in task descriptor --- inginious/common/tasks_problems.py | 1 + 1 file changed, 1 insertion(+) diff --git a/inginious/common/tasks_problems.py b/inginious/common/tasks_problems.py index 8a4d18ba29..e41d0330fe 100644 --- a/inginious/common/tasks_problems.py +++ b/inginious/common/tasks_problems.py @@ -187,6 +187,7 @@ def parse_problem(self, problem_content): offset = int(problem_content["offset"]) if offset < 1: raise Exception("Line offset must be positive!") + problem_content["offset"] = offset except ValueError: raise Exception("Line offset must be an integer!") return Problem.parse_problem(problem_content) From b35cb775bff9949b795f8368f935185655663088 Mon Sep 17 00:00:00 2001 From: Alexandre Doneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:08:48 +0100 Subject: [PATCH 11/12] [common/tasks_problems] Fixing empty input leading to code block starting at 0 --- inginious/common/tasks_problems.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inginious/common/tasks_problems.py b/inginious/common/tasks_problems.py index e41d0330fe..c9935240d6 100644 --- a/inginious/common/tasks_problems.py +++ b/inginious/common/tasks_problems.py @@ -182,7 +182,9 @@ def input_is_consistent(self, task_input, default_allowed_extension, default_max @classmethod def parse_problem(self, problem_content): # Checking problem edit inputs - if len(problem_content["offset"]) != 0: + if len(problem_content["offset"]) == 0: + problem_content["offset"] = 1 + else: try: offset = int(problem_content["offset"]) if offset < 1: @@ -190,6 +192,7 @@ def parse_problem(self, problem_content): problem_content["offset"] = offset except ValueError: raise Exception("Line offset must be an integer!") + return Problem.parse_problem(problem_content) @classmethod From 207af6ff26036013ac5f57906ca387e9a960188a Mon Sep 17 00:00:00 2001 From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:31:19 +0100 Subject: [PATCH 12/12] [common/tasks_problems] not storing offset when no offset given MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of setting it to 1 (default value) we do not store it Co-authored-by: Anthony Gégo --- inginious/common/tasks_problems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inginious/common/tasks_problems.py b/inginious/common/tasks_problems.py index c9935240d6..ca0ae88a85 100644 --- a/inginious/common/tasks_problems.py +++ b/inginious/common/tasks_problems.py @@ -183,7 +183,7 @@ def input_is_consistent(self, task_input, default_allowed_extension, default_max def parse_problem(self, problem_content): # Checking problem edit inputs if len(problem_content["offset"]) == 0: - problem_content["offset"] = 1 + del problem_content["offset"] else: try: offset = int(problem_content["offset"])