From 2a2452629101c701e2c8d84fde79eec8bbf76f46 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 14 Nov 2023 09:25:03 +0100
Subject: [PATCH 01/49] [frontend/tasksets] Changing course accessibility init
structure
Adding an "accessible_period" dict to define start and end period.
---
inginious/frontend/pages/marketplace.py | 1 +
inginious/frontend/pages/tasksets.py | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py
index a92640a80..e3988e1ff 100644
--- a/inginious/frontend/pages/marketplace.py
+++ b/inginious/frontend/pages/marketplace.py
@@ -83,6 +83,7 @@ def import_taskset(taskset, new_tasksetid, username, taskset_factory):
new_descriptor = {"description": old_descriptor.get("description", ""),
'admins': [username],
"accessible": False,
+ "accessible_period": {"start": None, "end": None},
"tags": old_descriptor.get("tags", {})}
if "name" in old_descriptor:
new_descriptor["name"] = old_descriptor["name"] + " - " + new_tasksetid
diff --git a/inginious/frontend/pages/tasksets.py b/inginious/frontend/pages/tasksets.py
index 27a03a80a..7f821af63 100644
--- a/inginious/frontend/pages/tasksets.py
+++ b/inginious/frontend/pages/tasksets.py
@@ -35,8 +35,8 @@ def POST_AUTH(self): # pylint: disable=arguments-differ
if self.user_manager.session_username() in taskset.get_admins() or taskset.is_public() or self.user_manager.user_is_superadmin():
task_dispenser = taskset.get_task_dispenser()
self.course_factory.create_course(courseid, {
- "name": courseid, "accessible": False, "tasksetid": taskset.get_id(),
- "admins": [self.user_manager.session_username()], "students": [],
+ "name": courseid, "accessible": False, "accessible_period": {"start": None, "end": None},
+ "tasksetid": taskset.get_id(), "admins": [self.user_manager.session_username()], "students": [],
"task_dispenser": task_dispenser.get_id(), "dispenser_data": task_dispenser.get_dispenser_data()
})
success = True
From 78449e807442f8d9c015d096094992b16747f0ad Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 14 Nov 2023 09:27:32 +0100
Subject: [PATCH 02/49] [frontend/accessible_time] Changing AccessibleTime to
new course/task accessibility and registration
Courses will now use a dictionnary for "accessible_period" and "registration_period" with a start and end element. Tasks will have an "accessibility_period" dict with an extra soft_end element.
---
inginious/frontend/accessible_time.py | 83 +++++++++++----------------
inginious/frontend/courses.py | 4 +-
inginious/frontend/tasks.py | 3 +-
3 files changed, 36 insertions(+), 54 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 4da1a370b..dcd888703 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -28,47 +28,30 @@ def parse_date(date, default=None):
class AccessibleTime(object):
""" represents the period of time when a course/task is accessible """
- def __init__(self, val=None):
+ def __init__(self, val=None, period=None):
"""
- Parse a string/a boolean to get the correct time period.
- Correct values for val:
- True (task always open)
- False (task always closed)
- 2014-07-16 11:24:00 (task is open from 2014-07-16 at 11:24:00)
- 2014-07-16 (task is open from 2014-07-16)
- / 2014-07-16 11:24:00 (task is only open before the 2014-07-16 at 11:24:00)
- / 2014-07-16 (task is only open before the 2014-07-16)
- 2014-07-16 11:24:00 / 2014-07-20 11:24:00 (task is open from 2014-07-16 11:24:00 and will be closed the 2014-07-20 at 11:24:00)
- 2014-07-16 / 2014-07-20 11:24:00 (...)
- 2014-07-16 11:24:00 / 2014-07-20 (...)
- 2014-07-16 / 2014-07-20 (...)
- 2014-07-16 11:24:00 / 2014-07-20 11:24:00 / 2014-07-20 12:24:00 (task is open from 2014-07-16 11:24:00, has a soft deadline set at 2014-07-20 11:24:00 and will be closed the 2014-07-20 at 11:24:00)
- 2014-07-16 / 2014-07-20 11:24:00 / 2014-07-21 (...)
- 2014-07-16 / 2014-07-20 / 2014-07-21 (...)
+ Used to represent the period of time when a course/task is accessible.
+ :param val : bool, optionnal, if False, it is never accessible, if True, it is always accessible or limited
+ by period dict
+ :param period : dict, contains start, end and optionally soft_end datetime objects
"""
+
+ period = period or {"start": None, "soft_end": None, "end": None}
+ if "soft_end" not in period.keys():
+ period["soft_end"] = None
+
if val is None or val == "" or val is True:
- self._val = [datetime.min, datetime.max]
- self._soft_end = datetime.max
- elif val == False:
- self._val = [datetime.max, datetime.max]
- self._soft_end = datetime.max
- else: # str
- values = val.split("/")
- if len(values) == 1:
- self._val = [parse_date(values[0].strip(), datetime.min), datetime.max]
- self._soft_end = datetime.max
- elif len(values) == 2:
- # Has start time and hard deadline
- self._val = [parse_date(values[0].strip(), datetime.min), parse_date(values[1].strip(), datetime.max)]
- self._soft_end = self._val[1]
+ if period["start"] or period["soft_end"] or period["end"]:
+ self._start = period.get("start", datetime.min)
+ self._end = period.get("end", datetime.max)
+ self._soft_end = period.get("soft_end", datetime.max)
else:
- # Has start time, soft deadline and hard deadline
- self._val = [parse_date(values[0].strip(), datetime.min), parse_date(values[2].strip(), datetime.max)]
- self._soft_end = parse_date(values[1].strip(), datetime.max)
-
- # Having a soft deadline after the hard one does not make sense, make soft-deadline same as hard-deadline
- if self._soft_end > self._val[1]:
- self._soft_end = self._val[1]
+ self._start = datetime.min
+ self._end, self._soft_end = datetime.max, datetime.max
+ else:
+ self._start = self._end = self._soft_end = datetime.max
+ if self._soft_end and self._end and self._soft_end > self._end:
+ self._soft_end = self._end
def before_start(self, when=None):
@@ -76,7 +59,7 @@ def before_start(self, when=None):
if when is None:
when = datetime.now()
- return self._val[0] > when
+ return self._start > when
def after_start(self, when=None):
""" Returns True if the task/course is or have been accessible in the past """
@@ -87,53 +70,51 @@ def is_open(self, when=None):
if when is None:
when = datetime.now()
- return self._val[0] <= when and when <= self._val[1]
+ return self._start <= when and when <= self._end
def is_open_with_soft_deadline(self, when=None):
""" Returns True if the course/task is still open with the soft deadline """
if when is None:
when = datetime.now()
- return self._val[0] <= when and when <= self._soft_end
+ return self._start <= when and when <= self._soft_end
def is_always_accessible(self):
""" Returns true if the course/task is always accessible """
- return self._val[0] == datetime.min and self._val[1] == datetime.max
+ return self._start == datetime.min and self._end == datetime.max
def is_never_accessible(self):
""" Returns true if the course/task is never accessible """
- return self._val[0] == datetime.max and self._val[1] == datetime.max
+ return self._start == datetime.max and self._end == datetime.max
def get_std_start_date(self):
""" If the date is custom, return the start datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
- first, _ = self._val
- if first != datetime.min and first != datetime.max:
- return first.strftime("%Y-%m-%d %H:%M:%S")
+ if self._start != datetime.min and self._start != datetime.max:
+ return self._start.strftime("%Y-%m-%d %H:%M:%S") if self._start is not None else ""
else:
return ""
def get_std_end_date(self):
""" If the date is custom, return the end datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
- _, second = self._val
- if second != datetime.max:
- return second.strftime("%Y-%m-%d %H:%M:%S")
+ if self._end != datetime.max:
+ return self._end.strftime("%Y-%m-%d %H:%M:%S") if self._end is not None else ""
else:
return ""
def get_std_soft_end_date(self):
""" If the date is custom, return the soft datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
if self._soft_end != datetime.max:
- return self._soft_end.strftime("%Y-%m-%d %H:%M:%S")
+ return self._soft_end.strftime("%Y-%m-%d %H:%M:%S") if self._soft_end is not None else ""
else:
return ""
def get_start_date(self):
""" Return a datetime object, representing the date when the task/course become accessible """
- return self._val[0]
+ return self._start
def get_end_date(self):
""" Return a datetime object, representing the deadline for accessibility """
- return self._val[1]
+ return self._end
def get_soft_end_date(self):
""" Return a datetime object, representing the soft deadline for accessibility """
diff --git a/inginious/frontend/courses.py b/inginious/frontend/courses.py
index 74fc6e1cc..b61a913b1 100644
--- a/inginious/frontend/courses.py
+++ b/inginious/frontend/courses.py
@@ -41,8 +41,8 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
self._admins = self._content.get('admins', [])
self._description = self._content.get('description', '')
- self._accessible = AccessibleTime(self._content.get("accessible", None))
- self._registration = AccessibleTime(self._content.get("registration", None))
+ self._accessible = AccessibleTime(self._content.get("accessible", None), self._content.get("accessible_period"))
+ self._registration = AccessibleTime(self._content.get("registration", None), self._content.get("registration_period"))
self._registration_password = self._content.get('registration_password', None)
self._registration_ac = self._content.get('registration_ac', None)
if self._registration_ac not in [None, "username", "binding", "email"]:
diff --git a/inginious/frontend/tasks.py b/inginious/frontend/tasks.py
index 4404d8595..36743de93 100644
--- a/inginious/frontend/tasks.py
+++ b/inginious/frontend/tasks.py
@@ -103,7 +103,8 @@ def __init__(self, taskset, taskid, content, plugin_manager, task_problem_types)
self._contact_url = ""
# _accessible
- self._accessible = AccessibleTime(self._data.get("accessible", None))
+ self._accessible = AccessibleTime(self._data.get("accessible", None),
+ self._data.get("accessible_period", {"start": None, "soft_end": None, "end": None}))
# Input random
self._input_random = int(self._data.get("input_random", 0))
From d8e22f8c5b6c10e24126c83ee0ee7753c799b4c7 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 14 Nov 2023 09:43:59 +0100
Subject: [PATCH 03/49] [frontend/course_admin] Adapting course settings form
to new course accessibility/registration structure
---
.../frontend/pages/course_admin/settings.py | 23 +++++++++++++++----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py
index fa230129f..c632792ca 100644
--- a/inginious/frontend/pages/course_admin/settings.py
+++ b/inginious/frontend/pages/course_admin/settings.py
@@ -5,6 +5,7 @@
import re
import flask
+from datetime import datetime
from inginious.common.base import dict_from_prefix, id_checker
from inginious.frontend.user_settings.field_types import FieldTypes
@@ -40,14 +41,21 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['groups_student_choice'] = True if data["groups_student_choice"] == "true" else False
if data["accessible"] == "custom":
- course_content['accessible'] = "{}/{}".format(data["accessible_start"], data["accessible_end"])
+ course_content['accessible'] = True
+ course_content['accessible_period'] = {}
+ course_content['accessible_period']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else None
+ course_content['accessible_period']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else None
elif data["accessible"] == "true":
course_content['accessible'] = True
+ course_content['accessible_period']["start"] = None
+ course_content['accessible_period']["end"] = None
else:
course_content['accessible'] = False
+ course_content['accessible_period']["start"] = None
+ course_content['accessible_period']["end"] = None
try:
- AccessibleTime(course_content['accessible'])
+ AccessibleTime(course_content['accessible'], course_content['accessible_period'])
except:
errors.append(_('Invalid accessibility dates'))
@@ -55,15 +63,20 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['allow_preview'] = True if data["allow_preview"] == "true" else False
if data["registration"] == "custom":
- course_content['registration'] = "{}/{}".format(data["registration_start"], data["registration_end"])
+ course_content['registration'] = True
+ course_content['registration_period'] = {}
+ course_content['registration_period']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else None
+ course_content['registration_period']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else None
elif data["registration"] == "true":
course_content['registration'] = True
+ course_content['registration_period']["start"] = None
+ course_content['registration_period']["end"] = None
else:
course_content['registration'] = False
try:
- AccessibleTime(course_content['registration'])
- except:
+ AccessibleTime(course_content['registration'], course_content['registration_period'])
+ except Exception:
errors.append(_('Invalid registration dates'))
course_content['registration_password'] = data['registration_password']
From ae87e9d0d96a8746f9d1456b665e2d8cef13d503 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 14 Nov 2023 10:04:19 +0100
Subject: [PATCH 04/49] [frontend/accessibility] Changing task accessibility
handling for task settings form
---
inginious/frontend/task_dispensers/toc.py | 7 +
.../config_items/accessibility.html | 128 ++++++++++--------
2 files changed, 80 insertions(+), 55 deletions(-)
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 98374102c..4ea2d4d5f 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -5,6 +5,7 @@
import copy
import inginious
from collections import OrderedDict
+from datetime import datetime
from functools import reduce
from operator import concat
@@ -116,6 +117,12 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
+ for task in self._task_config.values():
+ task['accessibility_period'] = {
+ key: value.strftime("%Y-%m-%d %H:%M:%S") if value is not None else ""
+ for key, value in task['accessibility_period'].items()
+ }
+
return template_helper.render("task_dispensers_admin/toc.html", element=element, course=course, taskset=taskset,
dispenser_structure=self._toc, dispenser_config=self._task_config, tasks=task_data,
task_errors=task_errors, config_fields=config_fields)
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index bb2032989..d35901172 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -85,19 +85,25 @@
var taskid = button.data('taskid');
if (!("accessibility" in dispenser_config[taskid]))
return;
- var accessibility = dispenser_config[taskid]["accessibility"];
+ general_taskid = taskid
+ var accessibility = dispenser_config[taskid]["accessibility"]
+ var accessibility_period = dispenser_config[taskid]["accessibility_period"] || {"start": "", "soft_end": "", "end": ""};
var value;
if (accessibility === true) {
- value = 'true';
- } else if (accessibility === false) {
+ if (accessibility_period["start"] || accessibility_period["soft_end"] || accessibility_period["end"]) {
+ console.log("in")
+ value = 'custom';
+
+ $('#accessibility_start_picker').datetimepicker('defaultDate', accessibility_period["start"]);
+ $('#accessibility_soft_end_picker').datetimepicker('defaultDate', accessibility_period["soft_end"]);
+ $('#accessibility_end_picker').datetimepicker('defaultDate', accessibility_period["end"]);
+
+ } else {
+ value = 'true';
+ }
+ } else{
value = 'false';
- } else {
- value = 'custom';
- var splitted_values = accessibility.split("/");
- $(this).find("#accessibility_start").val(splitted_values[0]);
- $(this).find("#accessibility_soft_end").val(splitted_values[1]);
- $(this).find("#accessibility_end").val(splitted_values[2]);
}
var field = $(this).find(".accessibility input[value=" + value + "]");
@@ -113,32 +119,43 @@
if(action == "accessibility") {
$("#task_" + taskid + " .accessibility").hide();
if($(this).val() == "true") {
- $("#task_" + taskid + " .accessibility-always").show();
- dispenser_config[taskid]["accessibility"] = true;
+ $("#task_" + general_taskid + " .accessibility-always").show();
+ dispenser_config[general_taskid]["accessibility"] = true;
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
} else if($(this).val() == "false") {
- $("#task_" + taskid + " .accessibility-never").show();
- dispenser_config[taskid]["accessibility"] = false;
+ $("#task_" + general_taskid + " .accessibility-never").show();
+ dispenser_config[general_taskid]["accessibility"] = false;
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
} else {
- $("#task_" + taskid + " .accessibility-custom").show();
+ $("#task_" + general_taskid + " .accessibility-custom").show();
+ dispenser_config[general_taskid]["accessibility"] = true;
var start = $("#edit_task_modal").find("#accessibility_start").val();
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[taskid]["accessibility"] = start + "/" + soft_end + "/" + end;
+
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
}
} else {
let id = $(this).attr("id");
- if (id == "accessibility_start")
- $("#task_" + taskid + " .accessibility-custom-start").text($(this).val());
- else if(id == "accessibility_end")
- $("#task_" + taskid + " .accessibility-custom-end").text($(this).val());
- else
- $("#task_" + taskid + " .accessibility-custom-soft-end").text($(this).val());
-
- var start = $("#edit_task_modal").find("#accessibility_start").val();
- var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[taskid]["accessibility"] = start + "/" + soft_end + "/" + end;
+
+ if (id == "accessibility_start") {
+ var start = $("#task_" + general_taskid + " .accessibility-custom-start").text($(this).val());
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
+ } else if(id == "accessibility_end") {
+ var end = $("#task_" + general_taskid + " .accessibility-custom-end").text($(this).val());
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
+ } else {
+ var soft_end = $("#task_" + general_taskid + " .accessibility-custom-soft-end").text($(this).val());
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
+ }
+ dispenser_config[general_taskid]["accessibility"] = true;
}
});
@@ -147,29 +164,23 @@
let val = $("#edit_task_modal #accessibility_start").val();
$("#task_" + taskid + " .accessibility-custom-start").text(val);
var start = $("#edit_task_modal").find("#accessibility_start").val();
- var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[taskid]["accessibility"] = start + "/" + soft_end + "/" + end;
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
});
$('#accessibility_end_picker').on("change.datetimepicker", function () {
var taskid = $("#edit_task_modal").data("taskid");
let val = $("#edit_task_modal #accessibility_end").val();
- $("#task_" + taskid + " .accessibility-custom-end").text(val);
- var start = $("#edit_task_modal").find("#accessibility_start").val();
- var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
+ $("#task_" + general_taskid + " .accessibility-custom-end").text(val);
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[taskid]["accessibility"] = start + "/" + soft_end + "/" + end;
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
});
$('#accessibility_soft_end_picker').on("change.datetimepicker", function () {
var taskid = $("#edit_task_modal").data("taskid");
let val = $("#edit_task_modal #accessibility_soft_end").val();
- $("#task_" + taskid + " .accessibility-custom-soft-end").text(val);
- var start = $("#edit_task_modal").find("#accessibility_start").val();
+ $("#task_" + general_taskid + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[taskid]["accessibility"] = start + "/" + soft_end + "/" + end;
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
});
// Catch grouped action changes
@@ -183,18 +194,27 @@
if(action == "accessibility") {
$("#task_" + taskid + " .accessibility").hide();
if(value == "true") {
- $("#task_" + taskid + " .accessibility-always").show();
- dispenser_config[taskid]["accessibility"] = true;
+ $("#task_" + general_taskid + " .accessibility-always").show();
+ dispenser_config[general_taskid]["accessibility"] = true;
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
} else if(value== "false") {
- $("#task_" + taskid + " .accessibility-never").show();
- dispenser_config[taskid]["accessibility"] = false;
+ $("#task_" + general_taskid + " .accessibility-never").show();
+ dispenser_config[general_taskid]["accessibility"] = false;
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
} else {
- $("#task_" + taskid + " .accessibility-custom").show();
+ $("#task_" + general_taskid + " .accessibility-custom").show();
+ dispenser_config[general_taskid]["accessibility"] = true;
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[taskid]["accessibility"] = start + "/" + soft_end + "/" + end;
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
}
} else {
if (id == "accessibility_start")
@@ -204,10 +224,14 @@
else
$("#task_" + taskid + " .accessibility-custom-soft-end").text(value);
+ dispenser_config[general_taskid]["accessibility"] = true;
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[taskid]["accessibility"] = start + "/" + soft_end + "/" + end;
+
+ dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
}
});
});
@@ -217,29 +241,23 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-start").text(val);
var start = $("#grouped-actions-edit #accessibility_start").val();
- var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
- var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("taskid")]["accessibility"] = start + "/" + soft_end + "/" + end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility_period"]["start"] = start;
});
});
$('#accessibility_end_picker_grouped').on("change.datetimepicker", function () {
let val = $("#grouped-actions-edit #accessibility_end").val();
$(".grouped-actions-task:checked").each(function () {
- $("#task_" + $(this).data("taskid") + " .accessibility-custom-end").text(val);
- var start = $("#grouped-actions-edit #accessibility_start").val();
- var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
+ $("#task_" + $(this).data("general_taskid") + " .accessibility-custom-end").text(val);
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("taskid")]["accessibility"] = start + "/" + soft_end + "/" + end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility_period"]["end"] = end;
});
});
$('#accessibility_soft_end_picker_grouped').on("change.datetimepicker", function () {
let val = $("#grouped-actions-edit #accessibility_soft_end").val();
$(".grouped-actions-task:checked").each(function () {
- $("#task_" + $(this).data("taskid") + " .accessibility-custom-soft-end").text(val);
- var start = $("#grouped-actions-edit #accessibility_start").val();
+ $("#task_" + $(this).data("general_taskid") + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
- var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("taskid")]["accessibility"] = start + "/" + soft_end + "/" + end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility_period"]["soft_end"] = soft_end;
});
});
From 5f95381350c893c83fc5f3bbf575842fbaf0a5a6 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 14 Nov 2023 10:11:54 +0100
Subject: [PATCH 05/49] [frontend/task_list] processing accessibility_period
before updating in DB
---
inginious/frontend/pages/course_admin/task_list.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index 71e85acaa..833a00675 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -8,6 +8,7 @@
import flask
from collections import OrderedDict
from natsort import natsorted
+from datetime import datetime
from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage
@@ -62,6 +63,11 @@ def update_dispenser(self, course, dispenser_data):
task_dispenser = course.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
+ for task in data['coonfig'].values():
+ task['accessibility_period'] = {
+ key: str(value) if value != "" else None
+ for key, value in task['accessibility_period'].items()
+ }
self.course_factory.update_course_descriptor_element(course.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.course_factory.update_course_descriptor_element(course.get_id(), 'dispenser_data', data)
From 3437e2e9e0025eeae0f512496c8dc7ddd641551d Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 14 Nov 2023 11:17:54 +0100
Subject: [PATCH 06/49] [frontend] Changing task accessibility data structure
Putting boolean and period dict inside one element : "accessibility": {"is_open": bool, "period": {"start": ..., "soft_end": ..., "end": ...}}. This way dispenser_data["config"] init in TableOfContents does not have to be changed.
---
.../frontend/pages/course_admin/task_list.py | 6 +-
inginious/frontend/task_dispensers/toc.py | 4 +-
inginious/frontend/task_dispensers/util.py | 5 +-
.../config_items/accessibility.html | 80 +++++++++----------
4 files changed, 48 insertions(+), 47 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index 833a00675..c9ee828e8 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -63,10 +63,10 @@ def update_dispenser(self, course, dispenser_data):
task_dispenser = course.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
- for task in data['coonfig'].values():
- task['accessibility_period'] = {
+ for task in data['config'].values():
+ task['accessibility']['period'] = {
key: str(value) if value != "" else None
- for key, value in task['accessibility_period'].items()
+ for key, value in task['accessibility']['period'].items()
}
self.course_factory.update_course_descriptor_element(course.get_id(), 'task_dispenser',
task_dispenser.get_id())
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 4ea2d4d5f..9d5a65605 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -118,9 +118,9 @@ def render_edit(self, template_helper, element, task_data, task_errors):
course = element if isinstance(element, inginious.frontend.courses.Course) else None
for task in self._task_config.values():
- task['accessibility_period'] = {
+ task['accessibility']['period'] = {
key: value.strftime("%Y-%m-%d %H:%M:%S") if value is not None else ""
- for key, value in task['accessibility_period'].items()
+ for key, value in task['accessibility']['period'].items()
}
return template_helper.render("task_dispensers_admin/toc.html", element=element, course=course, taskset=taskset,
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index 9af674bd6..d8aeef8a9 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -178,7 +178,8 @@ def get_value(cls, task_config):
class Accessibility(TaskConfigItem):
- default = False
+ default = {"is_open": False,
+ "period": {"start": None, "soft_end": None, "end": None}}
@classmethod
def get_template(cls):
@@ -196,7 +197,7 @@ def get_name(cls):
def get_value(cls, task_config):
accessibility = task_config.get(cls.get_id(), cls.default)
try:
- AccessibleTime(accessibility)
+ AccessibleTime(accessibility['is_open'], accessibility['period'])
except Exception as message:
raise InvalidTocException("Invalid task accessibility : {}".format(message))
return accessibility
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index d35901172..67e4afbb6 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -86,8 +86,8 @@
if (!("accessibility" in dispenser_config[taskid]))
return;
general_taskid = taskid
- var accessibility = dispenser_config[taskid]["accessibility"]
- var accessibility_period = dispenser_config[taskid]["accessibility_period"] || {"start": "", "soft_end": "", "end": ""};
+ var accessibility = dispenser_config[taskid]["accessibility"]["is_open"]
+ var accessibility_period = dispenser_config[taskid]["accessibility"]["period"] || {"start": "", "soft_end": "", "end": ""};
var value;
if (accessibility === true) {
@@ -120,42 +120,42 @@
$("#task_" + taskid + " .accessibility").hide();
if($(this).val() == "true") {
$("#task_" + general_taskid + " .accessibility-always").show();
- dispenser_config[general_taskid]["accessibility"] = true;
- dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
} else if($(this).val() == "false") {
$("#task_" + general_taskid + " .accessibility-never").show();
- dispenser_config[general_taskid]["accessibility"] = false;
- dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = false;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
} else {
$("#task_" + general_taskid + " .accessibility-custom").show();
- dispenser_config[general_taskid]["accessibility"] = true;
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
var start = $("#edit_task_modal").find("#accessibility_start").val();
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
- dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
}
} else {
let id = $(this).attr("id");
if (id == "accessibility_start") {
var start = $("#task_" + general_taskid + " .accessibility-custom-start").text($(this).val());
- dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
} else if(id == "accessibility_end") {
var end = $("#task_" + general_taskid + " .accessibility-custom-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
} else {
var soft_end = $("#task_" + general_taskid + " .accessibility-custom-soft-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
}
- dispenser_config[general_taskid]["accessibility"] = true;
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
}
});
@@ -164,7 +164,7 @@
let val = $("#edit_task_modal #accessibility_start").val();
$("#task_" + taskid + " .accessibility-custom-start").text(val);
var start = $("#edit_task_modal").find("#accessibility_start").val();
- dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
});
$('#accessibility_end_picker').on("change.datetimepicker", function () {
@@ -172,7 +172,7 @@
let val = $("#edit_task_modal #accessibility_end").val();
$("#task_" + general_taskid + " .accessibility-custom-end").text(val);
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
});
$('#accessibility_soft_end_picker').on("change.datetimepicker", function () {
@@ -180,7 +180,7 @@
let val = $("#edit_task_modal #accessibility_soft_end").val();
$("#task_" + general_taskid + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
});
// Catch grouped action changes
@@ -195,26 +195,26 @@
$("#task_" + taskid + " .accessibility").hide();
if(value == "true") {
$("#task_" + general_taskid + " .accessibility-always").show();
- dispenser_config[general_taskid]["accessibility"] = true;
- dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
} else if(value== "false") {
$("#task_" + general_taskid + " .accessibility-never").show();
- dispenser_config[general_taskid]["accessibility"] = false;
- dispenser_config[general_taskid]["accessibility_period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility_period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = false;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
} else {
$("#task_" + general_taskid + " .accessibility-custom").show();
- dispenser_config[general_taskid]["accessibility"] = true;
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
- dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
}
} else {
if (id == "accessibility_start")
@@ -224,14 +224,14 @@
else
$("#task_" + taskid + " .accessibility-custom-soft-end").text(value);
- dispenser_config[general_taskid]["accessibility"] = true;
+ dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[general_taskid]["accessibility_period"]["start"] = start;
- dispenser_config[general_taskid]["accessibility_period"]["soft_end"] = soft_end;
- dispenser_config[general_taskid]["accessibility_period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
}
});
});
@@ -241,7 +241,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-start").text(val);
var start = $("#grouped-actions-edit #accessibility_start").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility_period"]["start"] = start;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["period"]["start"] = start;
});
});
$('#accessibility_end_picker_grouped').on("change.datetimepicker", function () {
@@ -249,7 +249,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("general_taskid") + " .accessibility-custom-end").text(val);
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility_period"]["end"] = end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["period"]["end"] = end;
});
});
$('#accessibility_soft_end_picker_grouped').on("change.datetimepicker", function () {
@@ -257,7 +257,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("general_taskid") + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility_period"]["soft_end"] = soft_end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["period"]["soft_end"] = soft_end;
});
});
From 98e83964a305fd4ee449e9994f2525bacedca834 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 14 Nov 2023 15:53:41 +0100
Subject: [PATCH 07/49] [frontend] changing course acces and registration
structure in yaml and DB
To keep the same structure as the task accessibility (dict with bool and period dict).
---
inginious/frontend/courses.py | 10 +++---
.../frontend/pages/course_admin/settings.py | 36 +++++++++----------
inginious/frontend/pages/marketplace.py | 3 +-
inginious/frontend/pages/tasksets.py | 2 +-
inginious/frontend/task_dispensers/util.py | 2 +-
inginious/frontend/tasks.py | 4 +--
6 files changed, 28 insertions(+), 29 deletions(-)
diff --git a/inginious/frontend/courses.py b/inginious/frontend/courses.py
index b61a913b1..ea276593e 100644
--- a/inginious/frontend/courses.py
+++ b/inginious/frontend/courses.py
@@ -41,10 +41,12 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
self._admins = self._content.get('admins', [])
self._description = self._content.get('description', '')
- self._accessible = AccessibleTime(self._content.get("accessible", None), self._content.get("accessible_period"))
- self._registration = AccessibleTime(self._content.get("registration", None), self._content.get("registration_period"))
- self._registration_password = self._content.get('registration_password', None)
- self._registration_ac = self._content.get('registration_ac', None)
+ self._accessible = AccessibleTime(self._content['accessible'].get('is_open'),
+ self._content['accessible'].get('period'))
+ self._registration = AccessibleTime(self._content.get('registration', {}).get('is_open'),
+ self._content.get('registration', {}).get('period'))
+ self._registration_password = self._content.get('registration_password')
+ self._registration_ac = self._content.get('registration_ac')
if self._registration_ac not in [None, "username", "binding", "email"]:
raise Exception("Course has an invalid value for registration_ac: " + self.get_id())
self._registration_ac_accept = self._content.get('registration_ac_accept', True)
diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py
index c632792ca..7f155c2d8 100644
--- a/inginious/frontend/pages/course_admin/settings.py
+++ b/inginious/frontend/pages/course_admin/settings.py
@@ -41,41 +41,39 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['groups_student_choice'] = True if data["groups_student_choice"] == "true" else False
if data["accessible"] == "custom":
- course_content['accessible'] = True
- course_content['accessible_period'] = {}
- course_content['accessible_period']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else None
- course_content['accessible_period']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else None
+ course_content['accessible']['is_open'] = True
+ course_content['accessible']['period'] = {}
+ course_content['accessible']['period']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else None
+ course_content['accessible']['period']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else None
elif data["accessible"] == "true":
- course_content['accessible'] = True
- course_content['accessible_period']["start"] = None
- course_content['accessible_period']["end"] = None
+ course_content['accessible']['is_open'] = True
+ course_content['accessible']['period']["start"] = None
+ course_content['accessible']['period']["end"] = None
else:
- course_content['accessible'] = False
- course_content['accessible_period']["start"] = None
- course_content['accessible_period']["end"] = None
+ course_content['accessible']['is_open'] = False
+ course_content['accessible']['period']["start"] = None
+ course_content['accessible']['period']["end"] = None
try:
- AccessibleTime(course_content['accessible'], course_content['accessible_period'])
+ AccessibleTime(course_content['accessible']['is_open'], course_content['accessible']['period'])
except:
errors.append(_('Invalid accessibility dates'))
course_content['allow_unregister'] = True if data["allow_unregister"] == "true" else False
course_content['allow_preview'] = True if data["allow_preview"] == "true" else False
+ course_content['registration'] = {"period": {"start": None, "end": None}}
if data["registration"] == "custom":
- course_content['registration'] = True
- course_content['registration_period'] = {}
- course_content['registration_period']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else None
- course_content['registration_period']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else None
+ course_content['registration']['is_open'] = True
+ course_content['registration']['period']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else None
+ course_content['registration']['period']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else None
elif data["registration"] == "true":
- course_content['registration'] = True
- course_content['registration_period']["start"] = None
- course_content['registration_period']["end"] = None
+ course_content['registration']['is_open'] = True
else:
course_content['registration'] = False
try:
- AccessibleTime(course_content['registration'], course_content['registration_period'])
+ AccessibleTime(course_content['registration']['is_open'], course_content['registration']['period'])
except Exception:
errors.append(_('Invalid registration dates'))
diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py
index e3988e1ff..c1ab2d88a 100644
--- a/inginious/frontend/pages/marketplace.py
+++ b/inginious/frontend/pages/marketplace.py
@@ -82,8 +82,7 @@ def import_taskset(taskset, new_tasksetid, username, taskset_factory):
try:
new_descriptor = {"description": old_descriptor.get("description", ""),
'admins': [username],
- "accessible": False,
- "accessible_period": {"start": None, "end": None},
+ "accessible": {"is_open": False, "period": {"start": None, "end": None}},
"tags": old_descriptor.get("tags", {})}
if "name" in old_descriptor:
new_descriptor["name"] = old_descriptor["name"] + " - " + new_tasksetid
diff --git a/inginious/frontend/pages/tasksets.py b/inginious/frontend/pages/tasksets.py
index 7f821af63..b942b552e 100644
--- a/inginious/frontend/pages/tasksets.py
+++ b/inginious/frontend/pages/tasksets.py
@@ -35,7 +35,7 @@ def POST_AUTH(self): # pylint: disable=arguments-differ
if self.user_manager.session_username() in taskset.get_admins() or taskset.is_public() or self.user_manager.user_is_superadmin():
task_dispenser = taskset.get_task_dispenser()
self.course_factory.create_course(courseid, {
- "name": courseid, "accessible": False, "accessible_period": {"start": None, "end": None},
+ "name": courseid, "accessible": {"is_open": False, "period": {"start": None, "end": None}},
"tasksetid": taskset.get_id(), "admins": [self.user_manager.session_username()], "students": [],
"task_dispenser": task_dispenser.get_id(), "dispenser_data": task_dispenser.get_dispenser_data()
})
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index d8aeef8a9..97e93a0f8 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -197,7 +197,7 @@ def get_name(cls):
def get_value(cls, task_config):
accessibility = task_config.get(cls.get_id(), cls.default)
try:
- AccessibleTime(accessibility['is_open'], accessibility['period'])
+ AccessibleTime(*accessibility.values())
except Exception as message:
raise InvalidTocException("Invalid task accessibility : {}".format(message))
return accessibility
diff --git a/inginious/frontend/tasks.py b/inginious/frontend/tasks.py
index 36743de93..4d21083f7 100644
--- a/inginious/frontend/tasks.py
+++ b/inginious/frontend/tasks.py
@@ -103,8 +103,8 @@ def __init__(self, taskset, taskid, content, plugin_manager, task_problem_types)
self._contact_url = ""
# _accessible
- self._accessible = AccessibleTime(self._data.get("accessible", None),
- self._data.get("accessible_period", {"start": None, "soft_end": None, "end": None}))
+ self._accessible = AccessibleTime(self._data.get("accessible", {}).get("is_open"),
+ self._data.get("accessible", {}).get("period"))
# Input random
self._input_random = int(self._data.get("input_random", 0))
From 18a8daf45115c5e2f995c24c6c32185ea673adf8 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 16 Nov 2023 09:07:05 +0100
Subject: [PATCH 08/49] [frontend/toc] fixing dates transformation before
rendering toc.html
When initiating task_config from yaml we give a default value if there are missing values. When these values are dictionnaries they are actually all the same. Modifying one will modify all of them. Therefor we give an new instance at each default value to allow transformation into strings before rendering. Also had a mixup on the transformation itself.
---
inginious/frontend/pages/course_admin/task_list.py | 2 +-
inginious/frontend/task_dispensers/toc.py | 5 ++++-
inginious/frontend/task_dispensers/util.py | 8 ++++----
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index c9ee828e8..e2c5f1b65 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -65,7 +65,7 @@ def update_dispenser(self, course, dispenser_data):
if data:
for task in data['config'].values():
task['accessibility']['period'] = {
- key: str(value) if value != "" else None
+ key: datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if value != "" else None
for key, value in task['accessibility']['period'].items()
}
self.course_factory.update_course_descriptor_element(course.get_id(), 'task_dispenser',
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 9d5a65605..6c460e43d 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -38,6 +38,9 @@ def __init__(self, task_list_func, dispenser_data, database, element_id):
self._toc = SectionsList(dispenser_data.get("toc", {}))
self._task_config = dispenser_data.get("config", {})
parse_tasks_config(self._task_list_func().keys(), self.config_items, self._task_config)
+ #print(self._task_config)
+ #self._task_config['01_getting_started']['weight'] = 0
+ #print(self._task_config)
@classmethod
def get_id(cls):
@@ -119,7 +122,7 @@ def render_edit(self, template_helper, element, task_data, task_errors):
for task in self._task_config.values():
task['accessibility']['period'] = {
- key: value.strftime("%Y-%m-%d %H:%M:%S") if value is not None else ""
+ key: value.strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, datetime) else ""
for key, value in task['accessibility']['period'].items()
}
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index 97e93a0f8..de6bf1174 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -153,7 +153,7 @@ def get_value(cls, task_config):
class SubmissionLimit(TaskConfigItem):
- default = {"amount": -1, "period": -1}
+ default = dict({"amount": -1, "period": -1})
@classmethod
def get_template(cls):
@@ -178,8 +178,8 @@ def get_value(cls, task_config):
class Accessibility(TaskConfigItem):
- default = {"is_open": False,
- "period": {"start": None, "soft_end": None, "end": None}}
+ default = dict({"is_open": False,
+ "period": {"start": None, "soft_end": None, "end": None}})
@classmethod
def get_template(cls):
@@ -358,7 +358,7 @@ def parse_tasks_config(task_list, config_items, data):
# Set default empty dict for missing tasks
for taskid in task_list:
- data.setdefault(taskid, {})
+ data.setdefault(taskid, {}) # putting default dictionnary that are all the same ????
# Check each config validity
for taskid, structure in data.items():
From 7ae3a789b2b9318eada7cf5787e996622a993fb0 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Mon, 20 Nov 2023 14:45:34 +0100
Subject: [PATCH 09/49] [frontend] Storing dates in yaml files as datetime
objects
Adding representer to use !!timestamp tag in yaml for datetime objects. Moving string transformation in datetime before changing taskset yaml file. Fixing datetime transformation to datetime before toc.html render (was permanently changing task_config).
---
inginious/common/custom_yaml.py | 7 +++++++
inginious/frontend/pages/taskset_admin/template.py | 6 ++++++
inginious/frontend/task_dispensers/toc.py | 9 ++++-----
.../config_items/accessibility.html | 1 -
4 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/inginious/common/custom_yaml.py b/inginious/common/custom_yaml.py
index f86d0584b..721e3ddcb 100644
--- a/inginious/common/custom_yaml.py
+++ b/inginious/common/custom_yaml.py
@@ -5,6 +5,8 @@
""" A custom YAML based on PyYAML, that provides Ordered Dicts """
# Most ideas for this implementation comes from http://stackoverflow.com/questions/5121931/in-python-how-can-you-load-yaml-mappings-as-ordereddicts
+from datetime import datetime
+
from collections import OrderedDict
import yaml as original_yaml
@@ -74,10 +76,15 @@ def _long_str_representer(dumper, data):
def _default_representer(dumper, data):
return _long_str_representer(dumper, str(data))
+ def _timestamp_representer(dumper, data):
+ return dumper.represent_scalar('tag:yaml.org,2002:timestamp', data.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if data != "" else None)
+
+
OrderedDumper.add_representer(str, _long_str_representer)
OrderedDumper.add_representer(str, _long_str_representer)
OrderedDumper.add_representer(OrderedDict, _dict_representer)
OrderedDumper.add_representer(None, _default_representer)
+ OrderedDumper.add_representer(datetime, _timestamp_representer)
s = original_yaml.dump(data, stream, OrderedDumper, encoding='utf-8', allow_unicode=True, default_flow_style=False, indent=4, **kwds)
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index cc27bca01..7fde1d4ec 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -8,6 +8,7 @@
import flask
from collections import OrderedDict
from natsort import natsorted
+from datetime import datetime
from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage
@@ -61,6 +62,11 @@ def update_dispenser(self, taskset, dispenser_data):
task_dispenser = taskset.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
+ for task in dispenser_data.values():
+ task['accessibility']['period'] = {
+ key: datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if value != "" else None
+ for key, value in task['accessibility']['period'].items()
+ }
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'dispenser_data', data)
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 6c460e43d..9ab8c94bc 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -38,9 +38,6 @@ def __init__(self, task_list_func, dispenser_data, database, element_id):
self._toc = SectionsList(dispenser_data.get("toc", {}))
self._task_config = dispenser_data.get("config", {})
parse_tasks_config(self._task_list_func().keys(), self.config_items, self._task_config)
- #print(self._task_config)
- #self._task_config['01_getting_started']['weight'] = 0
- #print(self._task_config)
@classmethod
def get_id(cls):
@@ -120,14 +117,16 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
- for task in self._task_config.values():
+ _task_config = copy.deepcopy(self._task_config)
+
+ for task in _task_config.values():
task['accessibility']['period'] = {
key: value.strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, datetime) else ""
for key, value in task['accessibility']['period'].items()
}
return template_helper.render("task_dispensers_admin/toc.html", element=element, course=course, taskset=taskset,
- dispenser_structure=self._toc, dispenser_config=self._task_config, tasks=task_data,
+ dispenser_structure=self._toc, dispenser_config=_task_config, tasks=task_data,
task_errors=task_errors, config_fields=config_fields)
def render(self, template_helper, course, tasks_data, tag_list, username):
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index 67e4afbb6..b7489ecdf 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -92,7 +92,6 @@
if (accessibility === true) {
if (accessibility_period["start"] || accessibility_period["soft_end"] || accessibility_period["end"]) {
- console.log("in")
value = 'custom';
$('#accessibility_start_picker').datetimepicker('defaultDate', accessibility_period["start"]);
From 03401380c4a951e02aac2cbaca239677e4962db4 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 22 Nov 2023 08:55:02 +0100
Subject: [PATCH 10/49] [frontend/accessible_time] Changing AccessibleTime init
structure
Structure made in previous commits was not valid due to the way other method of this class work. Values for the dates could be None. Resulting in errors when comparing NoneType and datetime objects.
---
inginious/frontend/accessible_time.py | 22 +++++--------------
inginious/frontend/courses.py | 12 +++++-----
.../frontend/pages/course_admin/settings.py | 2 --
inginious/frontend/pages/tasksets.py | 1 +
inginious/frontend/task_dispensers/toc.py | 4 +++-
inginious/frontend/tasks.py | 4 ----
6 files changed, 16 insertions(+), 29 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index dcd888703..207b79fa7 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -36,22 +36,12 @@ def __init__(self, val=None, period=None):
:param period : dict, contains start, end and optionally soft_end datetime objects
"""
- period = period or {"start": None, "soft_end": None, "end": None}
- if "soft_end" not in period.keys():
- period["soft_end"] = None
-
- if val is None or val == "" or val is True:
- if period["start"] or period["soft_end"] or period["end"]:
- self._start = period.get("start", datetime.min)
- self._end = period.get("end", datetime.max)
- self._soft_end = period.get("soft_end", datetime.max)
- else:
- self._start = datetime.min
- self._end, self._soft_end = datetime.max, datetime.max
- else:
- self._start = self._end = self._soft_end = datetime.max
- if self._soft_end and self._end and self._soft_end > self._end:
- self._soft_end = self._end
+ self._start = period["start"] if period["start"] is not None else datetime.min
+ self._end = period["end"] if period["end"] is not None else datetime.max
+ if "soft_end" in period:
+ self._soft_end = period["soft_end"] if period["soft_end"] is not None else datetime.max
+ if self._soft_end > self._end:
+ self._soft_end = self._end
def before_start(self, when=None):
diff --git a/inginious/frontend/courses.py b/inginious/frontend/courses.py
index ea276593e..41f8b30f8 100644
--- a/inginious/frontend/courses.py
+++ b/inginious/frontend/courses.py
@@ -41,10 +41,10 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
self._admins = self._content.get('admins', [])
self._description = self._content.get('description', '')
- self._accessible = AccessibleTime(self._content['accessible'].get('is_open'),
- self._content['accessible'].get('period'))
- self._registration = AccessibleTime(self._content.get('registration', {}).get('is_open'),
- self._content.get('registration', {}).get('period'))
+ self._accessible = AccessibleTime(self._content['accessible']['is_open'],
+ self._content['accessible']['period'])
+ self._registration = AccessibleTime(self._content['registration']['is_open'],
+ self._content['registration']['period'])
self._registration_password = self._content.get('registration_password')
self._registration_ac = self._content.get('registration_ac')
if self._registration_ac not in [None, "username", "binding", "email"]:
@@ -76,8 +76,8 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
# Force some parameters if LTI is active
if self.is_lti():
- self._accessible = AccessibleTime(True)
- self._registration = AccessibleTime(False)
+ self._accessible = AccessibleTime(True,{"start": None, "end": None})
+ self._registration = AccessibleTime(False,{"start": None, "end": None})
self._registration_password = None
self._registration_ac = None
self._registration_ac_list = []
diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py
index 7f155c2d8..43ae1f9e7 100644
--- a/inginious/frontend/pages/course_admin/settings.py
+++ b/inginious/frontend/pages/course_admin/settings.py
@@ -42,7 +42,6 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
if data["accessible"] == "custom":
course_content['accessible']['is_open'] = True
- course_content['accessible']['period'] = {}
course_content['accessible']['period']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else None
course_content['accessible']['period']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else None
elif data["accessible"] == "true":
@@ -62,7 +61,6 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['allow_unregister'] = True if data["allow_unregister"] == "true" else False
course_content['allow_preview'] = True if data["allow_preview"] == "true" else False
- course_content['registration'] = {"period": {"start": None, "end": None}}
if data["registration"] == "custom":
course_content['registration']['is_open'] = True
course_content['registration']['period']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else None
diff --git a/inginious/frontend/pages/tasksets.py b/inginious/frontend/pages/tasksets.py
index b942b552e..c0ba1cf5a 100644
--- a/inginious/frontend/pages/tasksets.py
+++ b/inginious/frontend/pages/tasksets.py
@@ -36,6 +36,7 @@ def POST_AUTH(self): # pylint: disable=arguments-differ
task_dispenser = taskset.get_task_dispenser()
self.course_factory.create_course(courseid, {
"name": courseid, "accessible": {"is_open": False, "period": {"start": None, "end": None}},
+ "registration": {"is_open": False, "period": {"start": None, "end": None}},
"tasksetid": taskset.get_id(), "admins": [self.user_manager.session_username()], "students": [],
"task_dispenser": task_dispenser.get_id(), "dispenser_data": task_dispenser.get_dispenser_data()
})
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 9ab8c94bc..3aae44f31 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -71,7 +71,9 @@ def get_group_submission(self, taskid):
def get_accessibilities(self, taskids, usernames):
""" Get the accessible time of this task """
- return {username: {taskid: AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {})))
+
+ return {username: {taskid: AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {}))["is_open"],
+ Accessibility.get_value(self._task_config.get(taskid, {}))["period"])
for taskid in taskids } for username in usernames}
def get_categories(self, taskid):
diff --git a/inginious/frontend/tasks.py b/inginious/frontend/tasks.py
index 4d21083f7..ea46f0c68 100644
--- a/inginious/frontend/tasks.py
+++ b/inginious/frontend/tasks.py
@@ -102,10 +102,6 @@ def __init__(self, taskset, taskid, content, plugin_manager, task_problem_types)
else:
self._contact_url = ""
- # _accessible
- self._accessible = AccessibleTime(self._data.get("accessible", {}).get("is_open"),
- self._data.get("accessible", {}).get("period"))
-
# Input random
self._input_random = int(self._data.get("input_random", 0))
From 96231dd056ab0e31bd70e5f37ec8815591146f74 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 23 Nov 2023 16:10:42 +0100
Subject: [PATCH 11/49] [frontend] Storing datetime to string and the opposite
in util files
Plus fixing bug : When deleting tasks in course edit, the missing tasks were added back (check_dispenser_data function) with default parameters. Default date parameter beeing None it wasn't correctly handled by the date conversion (dispenser_data_str_to_datetimes function).
---
.../frontend/pages/course_admin/task_list.py | 8 ++------
.../frontend/pages/taskset_admin/template.py | 8 +++-----
inginious/frontend/pages/utils.py | 9 +++++++++
inginious/frontend/task_dispensers/toc.py | 13 +++----------
inginious/frontend/task_dispensers/util.py | 18 ++++++++++++++++--
5 files changed, 33 insertions(+), 23 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index e2c5f1b65..78bc131cf 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -11,7 +11,7 @@
from datetime import datetime
from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage
-
+from inginious.frontend.pages.utils import dispenser_data_str_to_datetimes
class CourseTaskListPage(INGIniousAdminPage):
""" List informations about all tasks """
@@ -63,11 +63,7 @@ def update_dispenser(self, course, dispenser_data):
task_dispenser = course.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
- for task in data['config'].values():
- task['accessibility']['period'] = {
- key: datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if value != "" else None
- for key, value in task['accessibility']['period'].items()
- }
+ dispenser_data_str_to_datetimes(data)
self.course_factory.update_course_descriptor_element(course.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.course_factory.update_course_descriptor_element(course.get_id(), 'dispenser_data', data)
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 7fde1d4ec..38b48453e 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -11,6 +11,8 @@
from datetime import datetime
from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage
+from inginious.frontend.pages.utils import dispenser_data_str_to_datetimes
+
class TasksetTemplatePage(INGIniousAdminPage):
@@ -62,11 +64,7 @@ def update_dispenser(self, taskset, dispenser_data):
task_dispenser = taskset.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
- for task in dispenser_data.values():
- task['accessibility']['period'] = {
- key: datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if value != "" else None
- for key, value in task['accessibility']['period'].items()
- }
+ dispenser_data_str_to_datetimes(data)
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'dispenser_data', data)
diff --git a/inginious/frontend/pages/utils.py b/inginious/frontend/pages/utils.py
index 40684b3e5..eec29fbac 100644
--- a/inginious/frontend/pages/utils.py
+++ b/inginious/frontend/pages/utils.py
@@ -13,6 +13,7 @@
from flask import redirect, url_for
from flask.views import MethodView
from werkzeug.exceptions import NotFound, NotAcceptable
+from datetime import datetime
from inginious.client.client import Client
from inginious.common import custom_yaml
@@ -364,3 +365,11 @@ def register_utils(database, user_manager, template_helper: TemplateHelper):
current_users, name, id, placeholder,
single)
)
+
+
+def dispenser_data_str_to_datetimes(dispenser_data):
+ for task in dispenser_data['config'].values():
+ task['accessibility']['period'] = {
+ key: datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "" and value is not None) else None
+ for key, value in task['accessibility']['period'].items()
+ }
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 3aae44f31..a2430d6f4 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -11,7 +11,7 @@
from operator import concat
from inginious.frontend.task_dispensers.util import check_toc, parse_tasks_config, check_task_config,\
SectionsList, SectionConfigItem, GroupSubmission, Weight, SubmissionStorage, EvaluationMode, Categories, \
- SubmissionLimit, Accessibility
+ SubmissionLimit, Accessibility, task_config_datetimes_to_str
from inginious.frontend.task_dispensers import TaskDispenser
from inginious.frontend.accessible_time import AccessibleTime
@@ -118,17 +118,10 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
-
- _task_config = copy.deepcopy(self._task_config)
-
- for task in _task_config.values():
- task['accessibility']['period'] = {
- key: value.strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, datetime) else ""
- for key, value in task['accessibility']['period'].items()
- }
+ task_config = task_config_datetimes_to_str(self._task_config)
return template_helper.render("task_dispensers_admin/toc.html", element=element, course=course, taskset=taskset,
- dispenser_structure=self._toc, dispenser_config=_task_config, tasks=task_data,
+ dispenser_structure=self._toc, dispenser_config=task_config, tasks=task_data,
task_errors=task_errors, config_fields=config_fields)
def render(self, template_helper, course, tasks_data, tag_list, username):
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index de6bf1174..2863bc6e6 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -2,6 +2,9 @@
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.
+import copy
+from datetime import datetime
+
from abc import ABCMeta, abstractmethod
from collections import namedtuple
from inginious.common.base import id_checker
@@ -358,7 +361,7 @@ def parse_tasks_config(task_list, config_items, data):
# Set default empty dict for missing tasks
for taskid in task_list:
- data.setdefault(taskid, {}) # putting default dictionnary that are all the same ????
+ data.setdefault(taskid, {})
# Check each config validity
for taskid, structure in data.items():
@@ -380,4 +383,15 @@ def check_task_config(task_list, config_items, data):
parse_tasks_config(task_list, config_items, data)
return True, ''
except Exception as ex:
- return False, str(ex)
\ No newline at end of file
+ return False, str(ex)
+
+
+def task_config_datetimes_to_str(task_config):
+
+ updated_task_config = copy.deepcopy(task_config)
+ for task in updated_task_config.values():
+ task['accessibility']['period'] = {
+ key: value.strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, datetime) else ""
+ for key, value in task['accessibility']['period'].items()
+ }
+ return updated_task_config
From fa0750a0a623773438bd930c7c940e3b98d32625 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 23 Nov 2023 16:17:00 +0100
Subject: [PATCH 12/49] [frontend] Adapting combinatory test to new
AccessibleTime structure
+ fixing dispenser_config not passed to render
---
inginious/frontend/accessible_time.py | 5 ++++-
.../task_dispensers/combinatory_test.py | 20 ++++++++++++-------
.../combinatory_test.html | 4 ++++
3 files changed, 21 insertions(+), 8 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 207b79fa7..bc6feb88a 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -28,7 +28,7 @@ def parse_date(date, default=None):
class AccessibleTime(object):
""" represents the period of time when a course/task is accessible """
- def __init__(self, val=None, period=None):
+ def __init__(self, is_open=None, period=None):
"""
Used to represent the period of time when a course/task is accessible.
:param val : bool, optionnal, if False, it is never accessible, if True, it is always accessible or limited
@@ -36,6 +36,9 @@ def __init__(self, val=None, period=None):
:param period : dict, contains start, end and optionally soft_end datetime objects
"""
+ if is_open is None or period is None:
+ raise Exception("AccessibleTime must be initialized with a boolean and a period dict")
+
self._start = period["start"] if period["start"] is not None else datetime.min
self._end = period["end"] if period["end"] is not None else datetime.max
if "soft_end" in period:
diff --git a/inginious/frontend/task_dispensers/combinatory_test.py b/inginious/frontend/task_dispensers/combinatory_test.py
index 0cae80bbf..59c4ccefa 100644
--- a/inginious/frontend/task_dispensers/combinatory_test.py
+++ b/inginious/frontend/task_dispensers/combinatory_test.py
@@ -4,10 +4,13 @@
# more information about the licensing of this file.
from random import Random
+import copy
+from datetime import datetime
+
import inginious
from inginious.frontend.task_dispensers.toc import TableOfContents
from inginious.frontend.task_dispensers.util import SectionConfigItem, Weight, SubmissionStorage, EvaluationMode, \
- Categories, SubmissionLimit, Accessibility
+ Categories, SubmissionLimit, Accessibility, task_config_datetimes_to_str
from inginious.frontend.accessible_time import AccessibleTime
@@ -27,17 +30,20 @@ def get_group_submission(self, taskid):
return False
def get_accessibilities(self, taskids, usernames):
- result = {username: {taskid: AccessibleTime(False) for taskid in taskids} for username in usernames}
+ result = {username: {taskid: AccessibleTime(False, {"start": None, "soft_end": None, "end": None})
+ for taskid in taskids} for username in usernames}
for index, section in enumerate(self._toc):
task_list = [taskid for taskid in section.get_tasks()
- if AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {}))).after_start()]
+ if AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {}))["is_open"],
+ Accessibility.get_value(self._task_config.get(taskid, {}))["period"]).after_start()]
amount_questions = int(section.get_config().get("amount", 0))
for username in usernames:
rand = Random("{}#{}#{}".format(username, index, section.get_title()))
random_order_choices = task_list.copy()
rand.shuffle(random_order_choices)
for taskid in random_order_choices[0:amount_questions]:
- result[username][taskid] = AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {})))
+ accessibility_values = Accessibility.get_value(self._task_config.get(taskid, {}))
+ result[username][taskid] = AccessibleTime(**accessibility_values)
return result
@@ -49,10 +55,10 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
+ task_config = task_config_datetimes_to_str(self._task_config)
- return template_helper.render("task_dispensers_admin/combinatory_test.html", element=element, course=course,
- taskset=taskset, dispenser_structure=self._toc, dispenser_config=self._task_config,
- tasks=task_data, task_errors=task_errors, config_fields=config_fields)
+ return template_helper.render("task_dispensers_admin/combinatory_test.html", element=element, course=course, taskset=taskset,
+ dispenser_structure=self._toc, dispenser_config=task_config, tasks=task_data, task_errors=task_errors, config_fields=config_fields)
def render(self, template_helper, course, tasks_data, tag_list, username):
""" Returns the formatted task list"""
diff --git a/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html b/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html
index 96e4b0750..8ce915a82 100644
--- a/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html
+++ b/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html
@@ -35,7 +35,11 @@
{{ _("Combinatory tests") }}
{% endwith %}
From f9aee73a4dbdf5954aca5b36c065dac434cf09ff Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 28 Nov 2023 10:33:11 +0100
Subject: [PATCH 13/49] [frontend] Changing datetime to str and opposite
Changing their name and structure to allow transformation of strings and dates inside python list.
---
.../frontend/pages/course_admin/task_list.py | 4 ++--
.../frontend/pages/taskset_admin/template.py | 4 ++--
inginious/frontend/pages/utils.py | 20 +++++++++++++------
.../task_dispensers/combinatory_test.py | 4 ++--
inginious/frontend/task_dispensers/toc.py | 4 ++--
inginious/frontend/task_dispensers/util.py | 20 ++++++++++---------
6 files changed, 33 insertions(+), 23 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index 78bc131cf..ebb35f539 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -11,7 +11,7 @@
from datetime import datetime
from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage
-from inginious.frontend.pages.utils import dispenser_data_str_to_datetimes
+from inginious.frontend.pages.utils import dict_data_str_to_datetimes
class CourseTaskListPage(INGIniousAdminPage):
""" List informations about all tasks """
@@ -63,7 +63,7 @@ def update_dispenser(self, course, dispenser_data):
task_dispenser = course.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
- dispenser_data_str_to_datetimes(data)
+ dict_data_str_to_datetimes(data)
self.course_factory.update_course_descriptor_element(course.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.course_factory.update_course_descriptor_element(course.get_id(), 'dispenser_data', data)
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 38b48453e..5ebb427cf 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -11,7 +11,7 @@
from datetime import datetime
from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage
-from inginious.frontend.pages.utils import dispenser_data_str_to_datetimes
+from inginious.frontend.pages.utils import dict_data_str_to_datetimes
@@ -64,7 +64,7 @@ def update_dispenser(self, taskset, dispenser_data):
task_dispenser = taskset.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
- dispenser_data_str_to_datetimes(data)
+ dict_data_str_to_datetimes(data)
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'dispenser_data', data)
diff --git a/inginious/frontend/pages/utils.py b/inginious/frontend/pages/utils.py
index eec29fbac..e658e50ee 100644
--- a/inginious/frontend/pages/utils.py
+++ b/inginious/frontend/pages/utils.py
@@ -367,9 +367,17 @@ def register_utils(database, user_manager, template_helper: TemplateHelper):
)
-def dispenser_data_str_to_datetimes(dispenser_data):
- for task in dispenser_data['config'].values():
- task['accessibility']['period'] = {
- key: datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "" and value is not None) else None
- for key, value in task['accessibility']['period'].items()
- }
+def dict_data_str_to_datetimes(data):
+ if isinstance(data, dict):
+ for key, value in data.items():
+ if isinstance(value, str):
+ try:
+ data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
+ except ValueError:
+ pass # If it's not a valid date string, continue without converting
+ else:
+ dict_data_str_to_datetimes(value)
+ elif isinstance(data, list):
+ for index, item in enumerate(data):
+ dict_data_str_to_datetimes(item)
+ return data
\ No newline at end of file
diff --git a/inginious/frontend/task_dispensers/combinatory_test.py b/inginious/frontend/task_dispensers/combinatory_test.py
index 59c4ccefa..5982aa8f0 100644
--- a/inginious/frontend/task_dispensers/combinatory_test.py
+++ b/inginious/frontend/task_dispensers/combinatory_test.py
@@ -10,7 +10,7 @@
import inginious
from inginious.frontend.task_dispensers.toc import TableOfContents
from inginious.frontend.task_dispensers.util import SectionConfigItem, Weight, SubmissionStorage, EvaluationMode, \
- Categories, SubmissionLimit, Accessibility, task_config_datetimes_to_str
+ Categories, SubmissionLimit, Accessibility, dict_data_datetimes_to_str
from inginious.frontend.accessible_time import AccessibleTime
@@ -55,7 +55,7 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
- task_config = task_config_datetimes_to_str(self._task_config)
+ task_config = dict_data_datetimes_to_str(self._task_config)
return template_helper.render("task_dispensers_admin/combinatory_test.html", element=element, course=course, taskset=taskset,
dispenser_structure=self._toc, dispenser_config=task_config, tasks=task_data, task_errors=task_errors, config_fields=config_fields)
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index a2430d6f4..5f06f75c9 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -11,7 +11,7 @@
from operator import concat
from inginious.frontend.task_dispensers.util import check_toc, parse_tasks_config, check_task_config,\
SectionsList, SectionConfigItem, GroupSubmission, Weight, SubmissionStorage, EvaluationMode, Categories, \
- SubmissionLimit, Accessibility, task_config_datetimes_to_str
+ SubmissionLimit, Accessibility, dict_data_datetimes_to_str
from inginious.frontend.task_dispensers import TaskDispenser
from inginious.frontend.accessible_time import AccessibleTime
@@ -118,7 +118,7 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
- task_config = task_config_datetimes_to_str(self._task_config)
+ task_config = dict_data_datetimes_to_str(self._task_config)
return template_helper.render("task_dispensers_admin/toc.html", element=element, course=course, taskset=taskset,
dispenser_structure=self._toc, dispenser_config=task_config, tasks=task_data,
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index 2863bc6e6..d23695361 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -386,12 +386,14 @@ def check_task_config(task_list, config_items, data):
return False, str(ex)
-def task_config_datetimes_to_str(task_config):
-
- updated_task_config = copy.deepcopy(task_config)
- for task in updated_task_config.values():
- task['accessibility']['period'] = {
- key: value.strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, datetime) else ""
- for key, value in task['accessibility']['period'].items()
- }
- return updated_task_config
+def dict_data_datetimes_to_str(data):
+ if isinstance(data, dict):
+ for key, value in data.items():
+ if isinstance(value, datetime):
+ data[key] = value.strftime("%Y-%m-%d %H:%M:%S")
+ else:
+ dict_data_datetimes_to_str(value)
+ elif isinstance(data, list):
+ for index, item in enumerate(data):
+ dict_data_datetimes_to_str(item)
+ return data
\ No newline at end of file
From 9c0473d635046d02f86374ca3505c8cd40559a83 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 30 Nov 2023 10:00:29 +0100
Subject: [PATCH 14/49] [frontend/task_dispenser_admin] Allowing string dates
in AccessibleTime for use in templates
Was setting up AccessibleTime to use datetime objects for the period dates. But in the templates the task_config is parsed in JSON and it can't parse datetime objects. So whe transform the dates into strings before render. AccessibleTime needs to be able to handle string dates because of it's use in the templates.
---
inginious/frontend/accessible_time.py | 11 +++++++++--
.../task_dispensers_admin/combinatory_test.html | 4 ----
.../frontend/templates/task_dispensers_admin/toc.html | 7 ++++---
3 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index bc6feb88a..e3de8db11 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -33,12 +33,19 @@ def __init__(self, is_open=None, period=None):
Used to represent the period of time when a course/task is accessible.
:param val : bool, optionnal, if False, it is never accessible, if True, it is always accessible or limited
by period dict
- :param period : dict, contains start, end and optionally soft_end datetime objects
+ :param period : dict, contains start, end and optionally soft_end as datetime objects or strings
"""
- if is_open is None or period is None:
+ if not isinstance(is_open, bool) or not isinstance(period, dict):
raise Exception("AccessibleTime must be initialized with a boolean and a period dict")
+ # transforming strings into datetimes in case AccessibleTime is used in html files (where datetime objects are not supported)
+ for key, date in period.items():
+ if isinstance(date, str) and date != "":
+ period[key] = parse_date(date)
+ elif isinstance(date, str) and date == "":
+ period[key] = None
+
self._start = period["start"] if period["start"] is not None else datetime.min
self._end = period["end"] if period["end"] is not None else datetime.max
if "soft_end" in period:
diff --git a/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html b/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html
index 8ce915a82..96e4b0750 100644
--- a/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html
+++ b/inginious/frontend/templates/task_dispensers_admin/combinatory_test.html
@@ -35,11 +35,7 @@ {{ _("Combinatory tests") }}
{% endwith %}
diff --git a/inginious/frontend/templates/task_dispensers_admin/toc.html b/inginious/frontend/templates/task_dispensers_admin/toc.html
index 43226a777..9364e48fc 100644
--- a/inginious/frontend/templates/task_dispensers_admin/toc.html
+++ b/inginious/frontend/templates/task_dispensers_admin/toc.html
@@ -1,6 +1,10 @@
{# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for #}
{# more information about the licensing of this file. #}
+
+
{% include "task_dispensers_admin/util.html" %}
@@ -24,6 +28,3 @@
{% include "task_dispensers_admin/empty_section.html" %}
{% endwith %}
-
From 0df58a457aa2f74be0702e40306682d4fb31083f Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 30 Nov 2023 11:22:39 +0100
Subject: [PATCH 15/49] [frontend/task_dispenser] Adapting
Accessibility.get_value() to legacy settings from task yaml
Was checking the accessibility settings as they were using the new structure (in DB or taskset.yaml). But when checking if the tasks have legacy settings in their task.yaml it raises an exception because the legacy accessibility has the old structure.
---
inginious/frontend/task_dispensers/util.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index d23695361..fed5fd036 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -199,10 +199,11 @@ def get_name(cls):
@classmethod
def get_value(cls, task_config):
accessibility = task_config.get(cls.get_id(), cls.default)
- try:
- AccessibleTime(*accessibility.values())
- except Exception as message:
- raise InvalidTocException("Invalid task accessibility : {}".format(message))
+ if isinstance(accessibility, dict):
+ try:
+ AccessibleTime(*accessibility.values())
+ except Exception as message:
+ raise InvalidTocException("Invalid task accessibility : {}".format(message))
return accessibility
From 1c7ba279a42afff0dc95f933a22a53733b1da397 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 6 Dec 2023 10:46:03 +0100
Subject: [PATCH 16/49] =?UTF-8?q?=C3=82[frontend]=20Remove=20boolean=20fro?=
=?UTF-8?q?m=20access=20data=20structure?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The boolean "is_open" is not really used. Instead we now only use the start, soft_end and end date. We will differenciate the access types by setting some dates to a minimal or maximal value. For example : a task that is always open will have a minimal start date and maximal soft_end and end dates.
In the custom acces mode, the dates are now optional. You can for example set an end date without setting a start date because it will automatically be set to the minimal value.
The dates are stored in MongoDB as ISODate() objects and we will use datetime.min and datetime.max in python code for minimal and maximal values. However, MongoDB stores dates with a precision to the millisecond. Datetime objects have a precision to the nanosecond. Therefor we have some transformations when retrieving dates from mongoDB (in AccessibleTime).
---
inginious/frontend/accessible_time.py | 34 ++++--
inginious/frontend/courses.py | 11 +-
.../frontend/pages/course_admin/settings.py | 30 +++---
inginious/frontend/pages/marketplace.py | 3 +-
inginious/frontend/pages/tasksets.py | 7 +-
inginious/frontend/pages/utils.py | 7 +-
.../task_dispensers/combinatory_test.py | 9 +-
inginious/frontend/task_dispensers/toc.py | 3 +-
inginious/frontend/task_dispensers/util.py | 5 +-
.../config_items/accessibility.html | 101 +++++++++---------
10 files changed, 113 insertions(+), 97 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index e3de8db11..954f70816 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -10,12 +10,15 @@
def parse_date(date, default=None):
""" Parse a valid date """
- if date == "":
+ if date == "" or not isinstance(date, str):
if default is not None:
return default
else:
raise Exception("Unknown format for " + date)
+ if date == "1-01-01 00:00:00":
+ return datetime.min
+
for format_type in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H", "%Y-%m-%d", "%d/%m/%Y %H:%M:%S", "%d/%m/%Y %H:%M", "%d/%m/%Y %H",
"%d/%m/%Y"]:
try:
@@ -28,7 +31,7 @@ def parse_date(date, default=None):
class AccessibleTime(object):
""" represents the period of time when a course/task is accessible """
- def __init__(self, is_open=None, period=None):
+ def __init__(self, period=None):
"""
Used to represent the period of time when a course/task is accessible.
:param val : bool, optionnal, if False, it is never accessible, if True, it is always accessible or limited
@@ -36,24 +39,37 @@ def __init__(self, is_open=None, period=None):
:param period : dict, contains start, end and optionally soft_end as datetime objects or strings
"""
- if not isinstance(is_open, bool) or not isinstance(period, dict):
- raise Exception("AccessibleTime must be initialized with a boolean and a period dict")
-
- # transforming strings into datetimes in case AccessibleTime is used in html files (where datetime objects are not supported)
+ # transforming strings into datetimes in case AccessibleTime is used in html files, where datetime objects are not supported
for key, date in period.items():
if isinstance(date, str) and date != "":
period[key] = parse_date(date)
elif isinstance(date, str) and date == "":
period[key] = None
- self._start = period["start"] if period["start"] is not None else datetime.min
- self._end = period["end"] if period["end"] is not None else datetime.max
+ self._start = self.adapt_database_date(period["start"])
+ self._end = self.adapt_database_date(period["end"])
if "soft_end" in period:
- self._soft_end = period["soft_end"] if period["soft_end"] is not None else datetime.max
+ self._soft_end = self.adapt_database_date(period["soft_end"])
if self._soft_end > self._end:
self._soft_end = self._end
+ def adapt_database_date(self, date):
+ """
+ Check if the date is the max or min DB value and transforms it into a datetime object.
+ MongoDB stores ISODate() objects in the database. When we store datetime.min or datetime.max in the database,
+ we will not get the same value back. It is because ISODate() objects store the date with a precision of
+ milliseconds, not nanoseconds like datetime objects.
+ :param date: datetime object coming from the database
+ """
+ if date == datetime(1, 1, 1, 0, 0, 0, 000000):
+ return datetime.min
+ elif date == datetime(9999, 12, 31, 23, 59, 59, 999000):
+ return datetime.max
+ else:
+ return date
+
+
def before_start(self, when=None):
""" Returns True if the task/course is not yet accessible """
if when is None:
diff --git a/inginious/frontend/courses.py b/inginious/frontend/courses.py
index 41f8b30f8..1417e0942 100644
--- a/inginious/frontend/courses.py
+++ b/inginious/frontend/courses.py
@@ -9,6 +9,7 @@
import gettext
import re
from typing import List
+from datetime import datetime
from inginious.frontend.user_settings.course_user_setting import CourseUserSetting
from inginious.common.tags import Tag
@@ -41,10 +42,8 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
self._admins = self._content.get('admins', [])
self._description = self._content.get('description', '')
- self._accessible = AccessibleTime(self._content['accessible']['is_open'],
- self._content['accessible']['period'])
- self._registration = AccessibleTime(self._content['registration']['is_open'],
- self._content['registration']['period'])
+ self._accessible = AccessibleTime(self._content['accessible'])
+ self._registration = AccessibleTime(self._content['registration'])
self._registration_password = self._content.get('registration_password')
self._registration_ac = self._content.get('registration_ac')
if self._registration_ac not in [None, "username", "binding", "email"]:
@@ -76,8 +75,8 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
# Force some parameters if LTI is active
if self.is_lti():
- self._accessible = AccessibleTime(True,{"start": None, "end": None})
- self._registration = AccessibleTime(False,{"start": None, "end": None})
+ self._accessible = AccessibleTime({"start": datetime.min, "end": datetime.max})
+ self._registration = AccessibleTime({"start": datetime.max, "end": datetime.max})
self._registration_password = None
self._registration_ac = None
self._registration_ac_list = []
diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py
index 43ae1f9e7..5558f79ca 100644
--- a/inginious/frontend/pages/course_admin/settings.py
+++ b/inginious/frontend/pages/course_admin/settings.py
@@ -41,20 +41,17 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['groups_student_choice'] = True if data["groups_student_choice"] == "true" else False
if data["accessible"] == "custom":
- course_content['accessible']['is_open'] = True
- course_content['accessible']['period']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else None
- course_content['accessible']['period']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else None
+ course_content['accessible']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else datetime.min
+ course_content['accessible']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else datetime.max
elif data["accessible"] == "true":
- course_content['accessible']['is_open'] = True
- course_content['accessible']['period']["start"] = None
- course_content['accessible']['period']["end"] = None
+ course_content['accessible']["start"] = datetime.min
+ course_content['accessible']["end"] = datetime.max
else:
- course_content['accessible']['is_open'] = False
- course_content['accessible']['period']["start"] = None
- course_content['accessible']['period']["end"] = None
+ course_content['accessible']["start"] = datetime.max
+ course_content['accessible']["end"] = datetime.max
try:
- AccessibleTime(course_content['accessible']['is_open'], course_content['accessible']['period'])
+ AccessibleTime(course_content['accessible'])
except:
errors.append(_('Invalid accessibility dates'))
@@ -62,16 +59,17 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['allow_preview'] = True if data["allow_preview"] == "true" else False
if data["registration"] == "custom":
- course_content['registration']['is_open'] = True
- course_content['registration']['period']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else None
- course_content['registration']['period']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else None
+ course_content['registration']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else datetime.min
+ course_content['registration']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else datetime.max
elif data["registration"] == "true":
- course_content['registration']['is_open'] = True
+ course_content['registration']["start"] = datetime.min
+ course_content['registration']["end"] = datetime.max
else:
- course_content['registration'] = False
+ course_content['registration']["start"] = datetime.max
+ course_content['registration']["end"] = datetime.max
try:
- AccessibleTime(course_content['registration']['is_open'], course_content['registration']['period'])
+ AccessibleTime(course_content['registration'])
except Exception:
errors.append(_('Invalid registration dates'))
diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py
index c1ab2d88a..4c6557d9c 100644
--- a/inginious/frontend/pages/marketplace.py
+++ b/inginious/frontend/pages/marketplace.py
@@ -8,6 +8,7 @@
import flask
from flask import redirect
from werkzeug.exceptions import Forbidden
+from datetime import datetime
from inginious.common.base import id_checker
from inginious.frontend.exceptions import ImportTasksetException
@@ -82,7 +83,7 @@ def import_taskset(taskset, new_tasksetid, username, taskset_factory):
try:
new_descriptor = {"description": old_descriptor.get("description", ""),
'admins': [username],
- "accessible": {"is_open": False, "period": {"start": None, "end": None}},
+ "accessible": {"start": datetime.max, "end": datetime.max},
"tags": old_descriptor.get("tags", {})}
if "name" in old_descriptor:
new_descriptor["name"] = old_descriptor["name"] + " - " + new_tasksetid
diff --git a/inginious/frontend/pages/tasksets.py b/inginious/frontend/pages/tasksets.py
index c0ba1cf5a..30c18e619 100644
--- a/inginious/frontend/pages/tasksets.py
+++ b/inginious/frontend/pages/tasksets.py
@@ -6,6 +6,7 @@
""" Index page """
import flask
from collections import OrderedDict
+from datetime import datetime
from inginious.frontend.pages.utils import INGIniousAuthPage
from inginious.frontend.exceptions import CourseAlreadyExistsException
@@ -35,9 +36,9 @@ def POST_AUTH(self): # pylint: disable=arguments-differ
if self.user_manager.session_username() in taskset.get_admins() or taskset.is_public() or self.user_manager.user_is_superadmin():
task_dispenser = taskset.get_task_dispenser()
self.course_factory.create_course(courseid, {
- "name": courseid, "accessible": {"is_open": False, "period": {"start": None, "end": None}},
- "registration": {"is_open": False, "period": {"start": None, "end": None}},
- "tasksetid": taskset.get_id(), "admins": [self.user_manager.session_username()], "students": [],
+ "name": courseid, "accessible": {"start": datetime.max, "end": datetime.max},
+ "registration": {"start": datetime.max, "end": datetime.max}, "tasksetid": taskset.get_id(),
+ "admins": [self.user_manager.session_username()], "students": [],
"task_dispenser": task_dispenser.get_id(), "dispenser_data": task_dispenser.get_dispenser_data()
})
success = True
diff --git a/inginious/frontend/pages/utils.py b/inginious/frontend/pages/utils.py
index e658e50ee..ce9d4058b 100644
--- a/inginious/frontend/pages/utils.py
+++ b/inginious/frontend/pages/utils.py
@@ -372,7 +372,12 @@ def dict_data_str_to_datetimes(data):
for key, value in data.items():
if isinstance(value, str):
try:
- data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
+ if value == "1-01-01 00:00:00":
+ data[key] = datetime.min
+ elif value == "9999-12-31 23:59:59":
+ data[key] = datetime.max
+ else:
+ data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
except ValueError:
pass # If it's not a valid date string, continue without converting
else:
diff --git a/inginious/frontend/task_dispensers/combinatory_test.py b/inginious/frontend/task_dispensers/combinatory_test.py
index 5982aa8f0..ec6ba578e 100644
--- a/inginious/frontend/task_dispensers/combinatory_test.py
+++ b/inginious/frontend/task_dispensers/combinatory_test.py
@@ -30,20 +30,19 @@ def get_group_submission(self, taskid):
return False
def get_accessibilities(self, taskids, usernames):
- result = {username: {taskid: AccessibleTime(False, {"start": None, "soft_end": None, "end": None})
+ result = {username: {taskid: AccessibleTime({"start": datetime.min, "soft_end": datetime.max, "end": datetime.max})
for taskid in taskids} for username in usernames}
for index, section in enumerate(self._toc):
task_list = [taskid for taskid in section.get_tasks()
- if AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {}))["is_open"],
- Accessibility.get_value(self._task_config.get(taskid, {}))["period"]).after_start()]
+ if AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {}))).after_start()]
amount_questions = int(section.get_config().get("amount", 0))
for username in usernames:
rand = Random("{}#{}#{}".format(username, index, section.get_title()))
random_order_choices = task_list.copy()
rand.shuffle(random_order_choices)
for taskid in random_order_choices[0:amount_questions]:
- accessibility_values = Accessibility.get_value(self._task_config.get(taskid, {}))
- result[username][taskid] = AccessibleTime(**accessibility_values)
+ accessibility_period = Accessibility.get_value(self._task_config.get(taskid, {}))
+ result[username][taskid] = AccessibleTime(accessibility_period)
return result
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 5f06f75c9..4ba8edd9d 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -72,8 +72,7 @@ def get_group_submission(self, taskid):
def get_accessibilities(self, taskids, usernames):
""" Get the accessible time of this task """
- return {username: {taskid: AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {}))["is_open"],
- Accessibility.get_value(self._task_config.get(taskid, {}))["period"])
+ return {username: {taskid: AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {})))
for taskid in taskids } for username in usernames}
def get_categories(self, taskid):
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index fed5fd036..df4f7e19f 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -181,8 +181,7 @@ def get_value(cls, task_config):
class Accessibility(TaskConfigItem):
- default = dict({"is_open": False,
- "period": {"start": None, "soft_end": None, "end": None}})
+ default = dict({"start": datetime.min, "soft_end": datetime.max, "end": datetime.max})
@classmethod
def get_template(cls):
@@ -201,7 +200,7 @@ def get_value(cls, task_config):
accessibility = task_config.get(cls.get_id(), cls.default)
if isinstance(accessibility, dict):
try:
- AccessibleTime(*accessibility.values())
+ AccessibleTime(accessibility)
except Exception as message:
raise InvalidTocException("Invalid task accessibility : {}".format(message))
return accessibility
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index b7489ecdf..c84179719 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -86,22 +86,24 @@
if (!("accessibility" in dispenser_config[taskid]))
return;
general_taskid = taskid
- var accessibility = dispenser_config[taskid]["accessibility"]["is_open"]
- var accessibility_period = dispenser_config[taskid]["accessibility"]["period"] || {"start": "", "soft_end": "", "end": ""};
+ var accessibility = dispenser_config[taskid]["accessibility"];
var value;
- if (accessibility === true) {
- if (accessibility_period["start"] || accessibility_period["soft_end"] || accessibility_period["end"]) {
- value = 'custom';
+ special_dates = ["1-01-01 00:00:00", "9999-12-31 23:59:59"]
+ function isSpecialDate(date) {
+ return special_dates.includes(date);
+ }
- $('#accessibility_start_picker').datetimepicker('defaultDate', accessibility_period["start"]);
- $('#accessibility_soft_end_picker').datetimepicker('defaultDate', accessibility_period["soft_end"]);
- $('#accessibility_end_picker').datetimepicker('defaultDate', accessibility_period["end"]);
+ if (Object.values(accessibility).some(date => !isSpecialDate(date))) {
+ value = 'custom';
- } else {
- value = 'true';
- }
- } else{
+ $('#accessibility_start_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["start"]) ? '' : accessibility["start"]);
+ $('#accessibility_soft_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["soft_end"]) ? '' : accessibility["soft_end"]);
+ $('#accessibility_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["end"]) ? '' : accessibility["end"]);
+
+ } else if (Object.values(accessibility).some(date => date == "1-01-01 00:00:00")) {
+ value = 'true';
+ } else {
value = 'false';
}
@@ -119,67 +121,68 @@
$("#task_" + taskid + " .accessibility").hide();
if($(this).val() == "true") {
$("#task_" + general_taskid + " .accessibility-always").show();
- dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else if($(this).val() == "false") {
$("#task_" + general_taskid + " .accessibility-never").show();
- dispenser_config[general_taskid]["accessibility"]["is_open"] = false;
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else {
$("#task_" + general_taskid + " .accessibility-custom").show();
- dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
var start = $("#edit_task_modal").find("#accessibility_start").val();
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
var end = $("#edit_task_modal").find("#accessibility_end").val();
-
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
}
} else {
let id = $(this).attr("id");
if (id == "accessibility_start") {
var start = $("#task_" + general_taskid + " .accessibility-custom-start").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["start"] = start;
} else if(id == "accessibility_end") {
var end = $("#task_" + general_taskid + " .accessibility-custom-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["end"] = end;
} else {
var soft_end = $("#task_" + general_taskid + " .accessibility-custom-soft-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end;
}
- dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
}
});
$('#accessibility_start_picker').on("change.datetimepicker", function () {
+ if( $("input[name='accessibility']:checked").val() != "custom")
+ return;
var taskid = $("#edit_task_modal").data("taskid");
let val = $("#edit_task_modal #accessibility_start").val();
$("#task_" + taskid + " .accessibility-custom-start").text(val);
var start = $("#edit_task_modal").find("#accessibility_start").val();
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["start"] = start;
});
$('#accessibility_end_picker').on("change.datetimepicker", function () {
+ if( $("input[name='accessibility']:checked").val() != "custom")
+ return;
var taskid = $("#edit_task_modal").data("taskid");
let val = $("#edit_task_modal #accessibility_end").val();
$("#task_" + general_taskid + " .accessibility-custom-end").text(val);
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["end"] = end;
});
$('#accessibility_soft_end_picker').on("change.datetimepicker", function () {
+ if( $("input[name='accessibility']:checked").val() != "custom")
+ return;
var taskid = $("#edit_task_modal").data("taskid");
let val = $("#edit_task_modal #accessibility_soft_end").val();
$("#task_" + general_taskid + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end;
});
// Catch grouped action changes
@@ -194,26 +197,23 @@
$("#task_" + taskid + " .accessibility").hide();
if(value == "true") {
$("#task_" + general_taskid + " .accessibility-always").show();
- dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else if(value== "false") {
$("#task_" + general_taskid + " .accessibility-never").show();
- dispenser_config[general_taskid]["accessibility"]["is_open"] = false;
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = "";
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = "";
+ dispenser_config[general_taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else {
$("#task_" + general_taskid + " .accessibility-custom").show();
- dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["end"] = end;
}
} else {
if (id == "accessibility_start")
@@ -223,14 +223,13 @@
else
$("#task_" + taskid + " .accessibility-custom-soft-end").text(value);
- dispenser_config[general_taskid]["accessibility"]["is_open"] = true;
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["period"]["start"] = start;
- dispenser_config[general_taskid]["accessibility"]["period"]["soft_end"] = soft_end;
- dispenser_config[general_taskid]["accessibility"]["period"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
}
});
});
@@ -240,7 +239,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-start").text(val);
var start = $("#grouped-actions-edit #accessibility_start").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["period"]["start"] = start;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["start"] = start;
});
});
$('#accessibility_end_picker_grouped').on("change.datetimepicker", function () {
@@ -248,7 +247,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("general_taskid") + " .accessibility-custom-end").text(val);
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["period"]["end"] = end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["end"] = end;
});
});
$('#accessibility_soft_end_picker_grouped').on("change.datetimepicker", function () {
@@ -256,7 +255,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("general_taskid") + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["period"]["soft_end"] = soft_end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["soft_end"] = soft_end;
});
});
From 2340aeac48c161ae8d8d5163d6d4829d525b0e15 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 6 Dec 2023 15:56:00 +0100
Subject: [PATCH 17/49] [common/custom_yaml] Changing timestamp representer to
correctly transform minimal date
When using the timestamp representer to transform datetime objects into strings with "!!timestamp" tags, the date.min caused problems. The zeros in front of the 1 (0001-01-01 00:00:00) would not be considered when using datetime.strftime(). It would simply delete the zeros causing the date to not have the correct structure for the timestamp.
---
inginious/common/custom_yaml.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/inginious/common/custom_yaml.py b/inginious/common/custom_yaml.py
index 721e3ddcb..0cc2a97c4 100644
--- a/inginious/common/custom_yaml.py
+++ b/inginious/common/custom_yaml.py
@@ -77,7 +77,10 @@ def _default_representer(dumper, data):
return _long_str_representer(dumper, str(data))
def _timestamp_representer(dumper, data):
- return dumper.represent_scalar('tag:yaml.org,2002:timestamp', data.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if data != "" else None)
+ formatted_date = data.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if data != "" else None
+ # Ensure the year, month and day are always correctly formatted
+ formatted_date = formatted_date.replace(data.strftime("%Y"), "{:04d}".format(data.year), 1)
+ return dumper.represent_scalar('tag:yaml.org,2002:timestamp', formatted_date)
OrderedDumper.add_representer(str, _long_str_representer)
From fd25ba3b997bb709d39b4f7b114ec58a6fe6754c Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 6 Dec 2023 16:53:24 +0100
Subject: [PATCH 18/49] [frontend/accessibility.html] Fix error occuring when
changing date on already custom accessibility
When changing a task accessibility (soft_end or end) that was already custom an error occured when saving the changes. It was because there was no check if the input value is an empty string.
---
.../config_items/accessibility.html | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index c84179719..426aa0e83 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -144,13 +144,13 @@
if (id == "accessibility_start") {
var start = $("#task_" + general_taskid + " .accessibility-custom-start").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
} else if(id == "accessibility_end") {
var end = $("#task_" + general_taskid + " .accessibility-custom-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
} else {
var soft_end = $("#task_" + general_taskid + " .accessibility-custom-soft-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
}
}
});
@@ -162,7 +162,7 @@
let val = $("#edit_task_modal #accessibility_start").val();
$("#task_" + taskid + " .accessibility-custom-start").text(val);
var start = $("#edit_task_modal").find("#accessibility_start").val();
- dispenser_config[general_taskid]["accessibility"]["start"] = start;
+ dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
});
$('#accessibility_end_picker').on("change.datetimepicker", function () {
@@ -172,7 +172,7 @@
let val = $("#edit_task_modal #accessibility_end").val();
$("#task_" + general_taskid + " .accessibility-custom-end").text(val);
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["end"] = end;
+ dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
});
$('#accessibility_soft_end_picker').on("change.datetimepicker", function () {
@@ -182,7 +182,7 @@
let val = $("#edit_task_modal #accessibility_soft_end").val();
$("#task_" + general_taskid + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end;
+ dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
});
// Catch grouped action changes
@@ -239,7 +239,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-start").text(val);
var start = $("#grouped-actions-edit #accessibility_start").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["start"] = start;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
});
});
$('#accessibility_end_picker_grouped').on("change.datetimepicker", function () {
@@ -247,7 +247,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("general_taskid") + " .accessibility-custom-end").text(val);
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["end"] = end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
});
});
$('#accessibility_soft_end_picker_grouped').on("change.datetimepicker", function () {
@@ -255,7 +255,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("general_taskid") + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["soft_end"] = soft_end;
+ dispenser_config[$(this).data("general_taskid")]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
});
});
From ed56453cc2817babdc91f2a8b2d773a13603fcbb Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Fri, 8 Dec 2023 10:01:43 +0100
Subject: [PATCH 19/49] [frontend] Fixing max date milliseconds not written in
yaml and DB
dict_data_str_to_datetime was not operating correctly because the strings were already transformed in datetime objects when checking for the data. Adapted AccessibleTime parse_date() and not using dict_data_str_to_datetime anymore.
---
inginious/frontend/accessible_time.py | 4 +++-
inginious/frontend/pages/course_admin/task_list.py | 1 -
inginious/frontend/pages/taskset_admin/template.py | 1 -
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 954f70816..b22712261 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -18,6 +18,8 @@ def parse_date(date, default=None):
if date == "1-01-01 00:00:00":
return datetime.min
+ if date == "9999-12-31 23:59:59":
+ return datetime.max
for format_type in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H", "%Y-%m-%d", "%d/%m/%Y %H:%M:%S", "%d/%m/%Y %H:%M", "%d/%m/%Y %H",
"%d/%m/%Y"]:
@@ -44,7 +46,7 @@ def __init__(self, period=None):
if isinstance(date, str) and date != "":
period[key] = parse_date(date)
elif isinstance(date, str) and date == "":
- period[key] = None
+ period[key] = None # don't want to transform in None ... Or maybe yes ? It could raise an error if the period given has a problem
self._start = self.adapt_database_date(period["start"])
self._end = self.adapt_database_date(period["end"])
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index ebb35f539..daddc296b 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -63,7 +63,6 @@ def update_dispenser(self, course, dispenser_data):
task_dispenser = course.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
- dict_data_str_to_datetimes(data)
self.course_factory.update_course_descriptor_element(course.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.course_factory.update_course_descriptor_element(course.get_id(), 'dispenser_data', data)
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 5ebb427cf..1b934de5b 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -64,7 +64,6 @@ def update_dispenser(self, taskset, dispenser_data):
task_dispenser = taskset.get_task_dispenser()
data, msg = task_dispenser.check_dispenser_data(dispenser_data)
if data:
- dict_data_str_to_datetimes(data)
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'task_dispenser',
task_dispenser.get_id())
self.taskset_factory.update_taskset_descriptor_element(taskset.get_id(), 'dispenser_data', data)
From 19c0500be56098e27dfc9ac69358dd4e35559f62 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Fri, 8 Dec 2023 10:47:27 +0100
Subject: [PATCH 20/49] [frontend/accessibility] Fixing grouped actions task
edit
---
.../config_items/accessibility.html | 88 +++++++++----------
1 file changed, 44 insertions(+), 44 deletions(-)
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index 426aa0e83..e81e3255c 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -85,7 +85,7 @@
var taskid = button.data('taskid');
if (!("accessibility" in dispenser_config[taskid]))
return;
- general_taskid = taskid
+ //general_taskid = taskid
var accessibility = dispenser_config[taskid]["accessibility"];
var value;
@@ -120,37 +120,37 @@
if(action == "accessibility") {
$("#task_" + taskid + " .accessibility").hide();
if($(this).val() == "true") {
- $("#task_" + general_taskid + " .accessibility-always").show();
- dispenser_config[general_taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ $("#task_" + taskid + " .accessibility-always").show();
+ dispenser_config[taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else if($(this).val() == "false") {
- $("#task_" + general_taskid + " .accessibility-never").show();
- dispenser_config[general_taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ $("#task_" + taskid + " .accessibility-never").show();
+ dispenser_config[taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else {
- $("#task_" + general_taskid + " .accessibility-custom").show();
+ $("#task_" + taskid + " .accessibility-custom").show();
var start = $("#edit_task_modal").find("#accessibility_start").val();
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
- dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
}
} else {
let id = $(this).attr("id");
if (id == "accessibility_start") {
- var start = $("#task_" + general_taskid + " .accessibility-custom-start").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ var start = $("#task_" + taskid + " .accessibility-custom-start").text($(this).val());
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
} else if(id == "accessibility_end") {
- var end = $("#task_" + general_taskid + " .accessibility-custom-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ var end = $("#task_" + taskid + " .accessibility-custom-end").text($(this).val());
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
} else {
- var soft_end = $("#task_" + general_taskid + " .accessibility-custom-soft-end").text($(this).val());
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ var soft_end = $("#task_" + taskid + " .accessibility-custom-soft-end").text($(this).val());
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
}
}
});
@@ -162,7 +162,7 @@
let val = $("#edit_task_modal #accessibility_start").val();
$("#task_" + taskid + " .accessibility-custom-start").text(val);
var start = $("#edit_task_modal").find("#accessibility_start").val();
- dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
});
$('#accessibility_end_picker').on("change.datetimepicker", function () {
@@ -170,9 +170,9 @@
return;
var taskid = $("#edit_task_modal").data("taskid");
let val = $("#edit_task_modal #accessibility_end").val();
- $("#task_" + general_taskid + " .accessibility-custom-end").text(val);
+ $("#task_" + taskid + " .accessibility-custom-end").text(val);
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
});
$('#accessibility_soft_end_picker').on("change.datetimepicker", function () {
@@ -180,9 +180,9 @@
return;
var taskid = $("#edit_task_modal").data("taskid");
let val = $("#edit_task_modal #accessibility_soft_end").val();
- $("#task_" + general_taskid + " .accessibility-custom-soft-end").text(val);
+ $("#task_" + taskid + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
});
// Catch grouped action changes
@@ -196,24 +196,24 @@
if(action == "accessibility") {
$("#task_" + taskid + " .accessibility").hide();
if(value == "true") {
- $("#task_" + general_taskid + " .accessibility-always").show();
- dispenser_config[general_taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ $("#task_" + taskid + " .accessibility-always").show();
+ dispenser_config[taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else if(value== "false") {
- $("#task_" + general_taskid + " .accessibility-never").show();
- dispenser_config[general_taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[general_taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ $("#task_" + taskid + " .accessibility-never").show();
+ dispenser_config[taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
} else {
- $("#task_" + general_taskid + " .accessibility-custom").show();
+ $("#task_" + taskid + " .accessibility-custom").show();
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["start"] = start;
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end;
- dispenser_config[general_taskid]["accessibility"]["end"] = end;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
}
} else {
if (id == "accessibility_start")
@@ -227,9 +227,9 @@
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[general_taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
- dispenser_config[general_taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
- dispenser_config[general_taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
}
});
});
@@ -239,23 +239,23 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-start").text(val);
var start = $("#grouped-actions-edit #accessibility_start").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[$(this).data("taskid")]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
});
});
$('#accessibility_end_picker_grouped').on("change.datetimepicker", function () {
let val = $("#grouped-actions-edit #accessibility_end").val();
$(".grouped-actions-task:checked").each(function () {
- $("#task_" + $(this).data("general_taskid") + " .accessibility-custom-end").text(val);
+ $("#task_" + $(this).data("taskid") + " .accessibility-custom-end").text(val);
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[$(this).data("taskid")]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
});
});
$('#accessibility_soft_end_picker_grouped').on("change.datetimepicker", function () {
let val = $("#grouped-actions-edit #accessibility_soft_end").val();
$(".grouped-actions-task:checked").each(function () {
- $("#task_" + $(this).data("general_taskid") + " .accessibility-custom-soft-end").text(val);
+ $("#task_" + $(this).data("taskid") + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
- dispenser_config[$(this).data("general_taskid")]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[$(this).data("taskid")]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
});
});
From a78963432755651d24ef951c4d62b1d56cc511b1 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 12 Dec 2023 11:31:01 +0100
Subject: [PATCH 21/49] [frontend] Changing accessible and registration
structure when importing legacy courses
+ moving dict_data_str_to_datetime()
---
inginious/frontend/course_factory.py | 83 +++++++++++++++++++
.../frontend/pages/course_admin/task_list.py | 2 -
.../frontend/pages/taskset_admin/template.py | 2 -
inginious/frontend/pages/utils.py | 20 -----
4 files changed, 83 insertions(+), 24 deletions(-)
diff --git a/inginious/frontend/course_factory.py b/inginious/frontend/course_factory.py
index 05e66d329..201fcc9cf 100644
--- a/inginious/frontend/course_factory.py
+++ b/inginious/frontend/course_factory.py
@@ -6,6 +6,7 @@
""" Factory for loading courses from disk """
from pymongo import ReturnDocument
+from datetime import datetime
from inginious.frontend.log import get_course_logger
@@ -13,6 +14,72 @@
from inginious.frontend.courses import Course
+# remove from pages/utils.py because only used here for the moment
+def dict_data_str_to_datetimes(data):
+ if isinstance(data, dict):
+ for key, value in data.items():
+ if isinstance(value, str):
+ try:
+ if value == "1-01-01 00:00:00":
+ data[key] = datetime.min
+ elif value == "9999-12-31 23:59:59":
+ data[key] = datetime.max
+ else:
+ data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
+ except ValueError:
+ pass # If it's not a valid date string, continue without converting
+ else:
+ dict_data_str_to_datetimes(value)
+ elif isinstance(data, list):
+ for index, item in enumerate(data):
+ dict_data_str_to_datetimes(item)
+ return data
+
+
+def change_access_structure(access_data, needs_soft_end=False):
+ """
+ Transforms old access structure (course access and registration, task access) into new structure.
+ ex: "accessible" can be a boolean or a concatenation of start and end dates ("start/end").
+ It will be transformed to have this structure:
+ "accessible": {"start": ..., "end": ...}
+ "registration": {"start": ..., "end": ...}
+ "accessibility": {"start": ...,"soft_end": ..., "end": ...}
+ When one of the dates is not given in a custom access or always/never accessible, it will be set to a max or min date.
+ examples:
+ "registration": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
+ "accessible": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
+ :param access_data: dict, old access structure
+ :param needs_soft_end: bool, True if the new structure needs a soft_end date in the structure
+ :return: dict, new access structure
+ """
+
+ new_access_data = {"start": None, "end": None}
+ # PK pas des objets datetime ? Les datetime seraint manipulés à l'écriture en YAML (et en DB si cette fonction est appelée à l'écriture en DB)
+
+
+ if isinstance(access_data, bool):
+ new_access_data["end"] = "9999-12-31 23:59:59"
+ if needs_soft_end:
+ new_access_data["soft_end"] = "9999-12-31 23:59:59"
+ if access_data:
+ new_access_data["start"] = "0001-01-01 00:00:00"
+ else:
+ new_access_data["start"] = "9999-12-31 23:59:59"
+
+
+ elif isinstance(access_data, str) and access_data != "":
+ dates = access_data.split("/")
+ if needs_soft_end:
+ new_access_data["start"] = dates[0]
+ new_access_data["soft_end"] = dates[1]
+ new_access_data["end"] = dates[2]
+ else:
+ new_access_data["start"] = dates[0]
+ new_access_data["end"] = dates[1]
+
+ return new_access_data
+
+
class CourseFactory(object):
""" Load courses from disk """
@@ -98,6 +165,11 @@ def _migrate_legacy_courses(self):
if taskset.is_legacy() and not self._database.courses.find_one({"_id": tasksetid}):
courseids.append(tasksetid)
+ # look here for bad accessibilitY structure ??? -> _migrate_legacy_courses detect the courses whithout taskset (taskset id in DB and correct taskset yaml file)
+ # I can maybe also checko here if the taskset file has the right structure (task accessibilities).
+
+ # where to check for DB structure ?
+
for courseid in courseids:
get_course_logger(courseid).warning("Trying to migrate legacy course {}.".format(courseid))
@@ -113,7 +185,18 @@ def _migrate_legacy_courses(self):
cleaned_taskset_descriptor["dispenser_data"] = taskset_descriptor.get("dispenser_data", {})
taskset_descriptor["tasksetid"] = courseid
taskset_descriptor["admins"] = taskset_descriptor.get("admins", []) + taskset_descriptor.get("tutors", [])
+ if "accessible" in taskset_descriptor:
+ taskset_descriptor["accessible"] = change_access_structure(taskset_descriptor["accessible"])
+ if "registration" in taskset_descriptor:
+ taskset_descriptor["registration"] = change_access_structure(taskset_descriptor["registration"])
+
+ # here transform task accessibilities ? -> no, it will be done during the migration of the taskset (import_legacy_tasks)
+ # task accessibilities are not in the course descriptor, but in the tasks descriptors (task.yaml)
+
+ taskset_descriptor = dict_data_str_to_datetimes(taskset_descriptor)
+ #cleaned_taskset_descriptor = dict_data_str_to_datetimes(cleaned_taskset_descriptor)
self._database.courses.update_one({"_id": courseid}, {"$set": taskset_descriptor}, upsert=True)
+ # why not set cleaned_taskset_descriptor ? -> parce qu'on transmet le taskset_descriptor pour l'enregistrer en DB
self._taskset_factory.update_taskset_descriptor_content(courseid, cleaned_taskset_descriptor)
except TasksetNotFoundException as e:
get_course_logger(courseid).warning("No migration from taskset possible for courseid {}.".format(courseid))
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index daddc296b..e06f462ca 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -8,10 +8,8 @@
import flask
from collections import OrderedDict
from natsort import natsorted
-from datetime import datetime
from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage
-from inginious.frontend.pages.utils import dict_data_str_to_datetimes
class CourseTaskListPage(INGIniousAdminPage):
""" List informations about all tasks """
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 1b934de5b..3b04b2082 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -8,10 +8,8 @@
import flask
from collections import OrderedDict
from natsort import natsorted
-from datetime import datetime
from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage
-from inginious.frontend.pages.utils import dict_data_str_to_datetimes
diff --git a/inginious/frontend/pages/utils.py b/inginious/frontend/pages/utils.py
index ce9d4058b..5c4007551 100644
--- a/inginious/frontend/pages/utils.py
+++ b/inginious/frontend/pages/utils.py
@@ -366,23 +366,3 @@ def register_utils(database, user_manager, template_helper: TemplateHelper):
single)
)
-
-def dict_data_str_to_datetimes(data):
- if isinstance(data, dict):
- for key, value in data.items():
- if isinstance(value, str):
- try:
- if value == "1-01-01 00:00:00":
- data[key] = datetime.min
- elif value == "9999-12-31 23:59:59":
- data[key] = datetime.max
- else:
- data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
- except ValueError:
- pass # If it's not a valid date string, continue without converting
- else:
- dict_data_str_to_datetimes(value)
- elif isinstance(data, list):
- for index, item in enumerate(data):
- dict_data_str_to_datetimes(item)
- return data
\ No newline at end of file
From 6ea6b0234a9de0e96bfcd2ef4e3c44bf3ca93595 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 13 Dec 2023 09:00:07 +0100
Subject: [PATCH 22/49] [frontend] Changing task access structure during
imports in task_dispenser
Changing access structure of tasks when importing in course template and task edit. Also moving change_access_structure, dict_data_str_to_datetime and dict_data_datetimes_to_str in util file in frontend directory.
---
inginious/frontend/course_factory.py | 65 +--------------
.../frontend/pages/course_admin/task_list.py | 4 +
.../frontend/pages/taskset_admin/template.py | 5 +-
.../task_dispensers/combinatory_test.py | 3 +-
inginious/frontend/task_dispensers/toc.py | 3 +-
inginious/frontend/task_dispensers/util.py | 12 ---
inginious/frontend/util.py | 83 +++++++++++++++++++
7 files changed, 96 insertions(+), 79 deletions(-)
create mode 100644 inginious/frontend/util.py
diff --git a/inginious/frontend/course_factory.py b/inginious/frontend/course_factory.py
index 201fcc9cf..4412b804b 100644
--- a/inginious/frontend/course_factory.py
+++ b/inginious/frontend/course_factory.py
@@ -12,72 +12,9 @@
from inginious.frontend.exceptions import CourseNotFoundException, CourseAlreadyExistsException, TasksetNotFoundException
from inginious.frontend.courses import Course
+from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes
-# remove from pages/utils.py because only used here for the moment
-def dict_data_str_to_datetimes(data):
- if isinstance(data, dict):
- for key, value in data.items():
- if isinstance(value, str):
- try:
- if value == "1-01-01 00:00:00":
- data[key] = datetime.min
- elif value == "9999-12-31 23:59:59":
- data[key] = datetime.max
- else:
- data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
- except ValueError:
- pass # If it's not a valid date string, continue without converting
- else:
- dict_data_str_to_datetimes(value)
- elif isinstance(data, list):
- for index, item in enumerate(data):
- dict_data_str_to_datetimes(item)
- return data
-
-
-def change_access_structure(access_data, needs_soft_end=False):
- """
- Transforms old access structure (course access and registration, task access) into new structure.
- ex: "accessible" can be a boolean or a concatenation of start and end dates ("start/end").
- It will be transformed to have this structure:
- "accessible": {"start": ..., "end": ...}
- "registration": {"start": ..., "end": ...}
- "accessibility": {"start": ...,"soft_end": ..., "end": ...}
- When one of the dates is not given in a custom access or always/never accessible, it will be set to a max or min date.
- examples:
- "registration": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
- "accessible": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
- :param access_data: dict, old access structure
- :param needs_soft_end: bool, True if the new structure needs a soft_end date in the structure
- :return: dict, new access structure
- """
-
- new_access_data = {"start": None, "end": None}
- # PK pas des objets datetime ? Les datetime seraint manipulés à l'écriture en YAML (et en DB si cette fonction est appelée à l'écriture en DB)
-
-
- if isinstance(access_data, bool):
- new_access_data["end"] = "9999-12-31 23:59:59"
- if needs_soft_end:
- new_access_data["soft_end"] = "9999-12-31 23:59:59"
- if access_data:
- new_access_data["start"] = "0001-01-01 00:00:00"
- else:
- new_access_data["start"] = "9999-12-31 23:59:59"
-
-
- elif isinstance(access_data, str) and access_data != "":
- dates = access_data.split("/")
- if needs_soft_end:
- new_access_data["start"] = dates[0]
- new_access_data["soft_end"] = dates[1]
- new_access_data["end"] = dates[2]
- else:
- new_access_data["start"] = dates[0]
- new_access_data["end"] = dates[1]
-
- return new_access_data
class CourseFactory(object):
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index e06f462ca..983d6294c 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -10,6 +10,7 @@
from natsort import natsorted
from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage
+from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes
class CourseTaskListPage(INGIniousAdminPage):
""" List informations about all tasks """
@@ -37,6 +38,9 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
task_dispenser = course.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
+ for taskid, task in data["config"].items():
+ task["accessibility"] = dict_data_str_to_datetimes(
+ change_access_structure(task["accessibility"], True))
self.update_dispenser(course, data)
except Exception as e:
errors.append(_("Something wrong happened: ") + str(e))
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 3b04b2082..6805e7a6f 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -10,7 +10,7 @@
from natsort import natsorted
from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage
-
+from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes
class TasksetTemplatePage(INGIniousAdminPage):
@@ -39,6 +39,9 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ
task_dispenser = taskset.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
+ for taskid, task in data["config"].items():
+ task["accessibility"] = dict_data_str_to_datetimes(
+ change_access_structure(task["accessibility"], True))
self.update_dispenser(taskset, data)
except Exception as e:
errors.append(_("Something wrong happened: ") + str(e))
diff --git a/inginious/frontend/task_dispensers/combinatory_test.py b/inginious/frontend/task_dispensers/combinatory_test.py
index ec6ba578e..c6c046c4a 100644
--- a/inginious/frontend/task_dispensers/combinatory_test.py
+++ b/inginious/frontend/task_dispensers/combinatory_test.py
@@ -10,8 +10,9 @@
import inginious
from inginious.frontend.task_dispensers.toc import TableOfContents
from inginious.frontend.task_dispensers.util import SectionConfigItem, Weight, SubmissionStorage, EvaluationMode, \
- Categories, SubmissionLimit, Accessibility, dict_data_datetimes_to_str
+ Categories, SubmissionLimit, Accessibility
from inginious.frontend.accessible_time import AccessibleTime
+from inginious.frontend.util import dict_data_datetimes_to_str
class CombinatoryTest(TableOfContents):
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 4ba8edd9d..3a2f185f6 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -11,9 +11,10 @@
from operator import concat
from inginious.frontend.task_dispensers.util import check_toc, parse_tasks_config, check_task_config,\
SectionsList, SectionConfigItem, GroupSubmission, Weight, SubmissionStorage, EvaluationMode, Categories, \
- SubmissionLimit, Accessibility, dict_data_datetimes_to_str
+ SubmissionLimit, Accessibility
from inginious.frontend.task_dispensers import TaskDispenser
from inginious.frontend.accessible_time import AccessibleTime
+from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes, dict_data_datetimes_to_str
class TableOfContents(TaskDispenser):
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index df4f7e19f..f089f0da6 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -385,15 +385,3 @@ def check_task_config(task_list, config_items, data):
except Exception as ex:
return False, str(ex)
-
-def dict_data_datetimes_to_str(data):
- if isinstance(data, dict):
- for key, value in data.items():
- if isinstance(value, datetime):
- data[key] = value.strftime("%Y-%m-%d %H:%M:%S")
- else:
- dict_data_datetimes_to_str(value)
- elif isinstance(data, list):
- for index, item in enumerate(data):
- dict_data_datetimes_to_str(item)
- return data
\ No newline at end of file
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
new file mode 100644
index 000000000..bc34264fa
--- /dev/null
+++ b/inginious/frontend/util.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
+# more information about the licensing of this file.
+
+from datetime import datetime
+
+
+
+def dict_data_datetimes_to_str(data):
+ if isinstance(data, dict):
+ for key, value in data.items():
+ if isinstance(value, datetime):
+ data[key] = value.strftime("%Y-%m-%d %H:%M:%S")
+ else:
+ dict_data_datetimes_to_str(value)
+ elif isinstance(data, list):
+ for index, item in enumerate(data):
+ dict_data_datetimes_to_str(item)
+ return data
+
+
+def dict_data_str_to_datetimes(data):
+ if isinstance(data, dict):
+ for key, value in data.items():
+ if isinstance(value, str):
+ try:
+ if value == "1-01-01 00:00:00":
+ data[key] = datetime.min
+ elif value == "9999-12-31 23:59:59":
+ data[key] = datetime.max
+ else:
+ data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
+ except ValueError:
+ pass # If it's not a valid date string, continue without converting
+ else:
+ dict_data_str_to_datetimes(value)
+ elif isinstance(data, list):
+ for index, item in enumerate(data):
+ dict_data_str_to_datetimes(item)
+ return data
+
+
+def change_access_structure(access_data, needs_soft_end=False):
+ """
+ Transforms old access structure (course access and registration, task access) into new structure.
+ ex: "accessible" can be a boolean or a concatenation of start and end dates ("start/end").
+ It will be transformed to have this structure:
+ "accessible": {"start": ..., "end": ...}
+ "registration": {"start": ..., "end": ...}
+ "accessibility": {"start": ...,"soft_end": ..., "end": ...}
+ When one of the dates is not given in a custom access or always/never accessible, it will be set to a max or min date.
+ examples:
+ "registration": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
+ "accessible": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
+ :param access_data: dict, old access structure
+ :param needs_soft_end: bool, True if the new structure needs a soft_end date in the structure
+ :return: dict, new access structure
+ """
+
+ new_access_data = {"start": None, "end": None}
+
+ if isinstance(access_data, bool):
+ new_access_data["end"] = "9999-12-31 23:59:59"
+ if needs_soft_end:
+ new_access_data["soft_end"] = "9999-12-31 23:59:59"
+ if access_data:
+ new_access_data["start"] = "0001-01-01 00:00:00"
+ else:
+ new_access_data["start"] = "9999-12-31 23:59:59"
+
+
+ elif isinstance(access_data, str) and access_data != "":
+ dates = access_data.split("/")
+ if needs_soft_end:
+ new_access_data["start"] = dates[0]
+ new_access_data["soft_end"] = dates[1]
+ new_access_data["end"] = dates[2]
+ else:
+ new_access_data["start"] = dates[0]
+ new_access_data["end"] = dates[1]
+
+ return new_access_data
\ No newline at end of file
From 0fe5ec1e6902a4ba12600131be90e3b888f4752b Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 13 Dec 2023 10:45:14 +0100
Subject: [PATCH 23/49] [plugins/contest] fixing method name change from
TasksetFactory
---
inginious/frontend/plugins/contests/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/inginious/frontend/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py
index 8fe079c1a..400f2116d 100644
--- a/inginious/frontend/plugins/contests/__init__.py
+++ b/inginious/frontend/plugins/contests/__init__.py
@@ -191,9 +191,9 @@ class ContestAdmin(INGIniousAdminPage):
def save_contest_data(self, course, contest_data):
""" Saves updated contest data for the course """
- course_content = self.taskset_factory.get_course_descriptor_content(course.get_id())
+ course_content = self.taskset_factory.get_taskset_descriptor_content(course.get_id())
course_content["dispenser_data"]["contest_settings"] = contest_data
- self.taskset_factory.update_course_descriptor_content(course.get_id(), course_content)
+ self.taskset_factory.update_taskset_descriptor_content(course.get_id(), course_content)
def GET_AUTH(self, courseid): # pylint: disable=arguments-differ
""" GET request: simply display the form """
From 1d5087046d7011b300c6e4ab98b7ca608a430be0 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 13 Dec 2023 11:35:34 +0100
Subject: [PATCH 24/49] [plugins/contests] Fixing contest edits saved in
taskset.yaml instead of DB
---
inginious/frontend/plugins/contests/__init__.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/inginious/frontend/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py
index 400f2116d..3a9f8f826 100644
--- a/inginious/frontend/plugins/contests/__init__.py
+++ b/inginious/frontend/plugins/contests/__init__.py
@@ -34,9 +34,9 @@ def __init__(self, task_list_func, dispenser_data, database, course_id):
TableOfContents.__init__(self, task_list_func, dispenser_data.get("toc_data", {}), database, course_id)
self._contest_settings = dispenser_data.get(
'contest_settings',
- {"enabled": False,
- "start": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- "end": (datetime.now() + timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S"),
+ {"enabled": False, # enabled false ?
+ "start": datetime.now(),
+ "end": datetime.now() + timedelta(hours=1),
"blackout": 0,
"penalty": 20}
)
@@ -191,9 +191,9 @@ class ContestAdmin(INGIniousAdminPage):
def save_contest_data(self, course, contest_data):
""" Saves updated contest data for the course """
- course_content = self.taskset_factory.get_taskset_descriptor_content(course.get_id())
+ course_content = self.course_factory.get_course_descriptor_content(course.get_id())
course_content["dispenser_data"]["contest_settings"] = contest_data
- self.taskset_factory.update_taskset_descriptor_content(course.get_id(), course_content)
+ self.course_factory.update_course_descriptor_content(course.get_id(), course_content)
def GET_AUTH(self, courseid): # pylint: disable=arguments-differ
""" GET request: simply display the form """
From 7a7d03f7e3d38ac0f66de042074a28cc3130b342 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 13 Dec 2023 12:01:16 +0100
Subject: [PATCH 25/49] [plugins/contests] Adapting contest plugin to new
access structure
---
inginious/frontend/plugins/contests/__init__.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/inginious/frontend/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py
index 3a9f8f826..56348589f 100644
--- a/inginious/frontend/plugins/contests/__init__.py
+++ b/inginious/frontend/plugins/contests/__init__.py
@@ -57,7 +57,8 @@ def check_dispenser_data(self, dispenser_data):
def get_accessibilities(self, taskids, usernames): # pylint: disable=unused-argument
contest_data = self.get_contest_data()
if contest_data['enabled']:
- return {username: {taskid: AccessibleTime(contest_data['start'] + '/') for taskid in taskids} for username in usernames}
+ accessibility = {"start": contest_data['start'], "end": contest_data['end']}
+ return {username: {taskid: AccessibleTime(accessibility) for taskid in taskids} for username in usernames}
else:
return TableOfContents.get_accessibilities(self, taskids, usernames)
@@ -221,17 +222,17 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
contest_data['end'] = new_data["end"]
try:
- start = datetime.strptime(contest_data['start'], "%Y-%m-%d %H:%M:%S")
+ contest_data['start'] = datetime.strptime(contest_data['start'], "%Y-%m-%d %H:%M:%S")
except:
errors.append('Invalid start date')
try:
- end = datetime.strptime(contest_data['end'], "%Y-%m-%d %H:%M:%S")
+ contest_data['end'] = datetime.strptime(contest_data['end'], "%Y-%m-%d %H:%M:%S")
except:
errors.append('Invalid end date')
if len(errors) == 0:
- if start >= end:
+ if contest_data['start'] >= contest_data['end']:
errors.append('Start date should be before end date')
try:
From 7a6b750219a61be6eff72bfc5934d7f53ad95a6b Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 13 Dec 2023 15:14:23 +0100
Subject: [PATCH 26/49] [frontend/accessible_time] Refactoring parameter check
for AccessibleTime
---
inginious/frontend/accessible_time.py | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index b22712261..597380ef3 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -33,7 +33,7 @@ def parse_date(date, default=None):
class AccessibleTime(object):
""" represents the period of time when a course/task is accessible """
- def __init__(self, period=None):
+ def __init__(self, period):
"""
Used to represent the period of time when a course/task is accessible.
:param val : bool, optionnal, if False, it is never accessible, if True, it is always accessible or limited
@@ -41,12 +41,17 @@ def __init__(self, period=None):
:param period : dict, contains start, end and optionally soft_end as datetime objects or strings
"""
+ if not isinstance(period, dict):
+ raise Exception("Wrong period given to AccessibleTime")
+
# transforming strings into datetimes in case AccessibleTime is used in html files, where datetime objects are not supported
for key, date in period.items():
if isinstance(date, str) and date != "":
period[key] = parse_date(date)
- elif isinstance(date, str) and date == "":
- period[key] = None # don't want to transform in None ... Or maybe yes ? It could raise an error if the period given has a problem
+ elif not isinstance(date, (datetime, str)):
+ raise Exception("Wrong period given to AccessibleTime")
+ elif date == "":
+ raise Exception("Empty date given to AccessibleTime")
self._start = self.adapt_database_date(period["start"])
self._end = self.adapt_database_date(period["end"])
@@ -108,21 +113,21 @@ def is_never_accessible(self):
def get_std_start_date(self):
""" If the date is custom, return the start datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
if self._start != datetime.min and self._start != datetime.max:
- return self._start.strftime("%Y-%m-%d %H:%M:%S") if self._start is not None else ""
+ return self._start.strftime("%Y-%m-%d %H:%M:%S")
else:
return ""
def get_std_end_date(self):
""" If the date is custom, return the end datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
if self._end != datetime.max:
- return self._end.strftime("%Y-%m-%d %H:%M:%S") if self._end is not None else ""
+ return self._end.strftime("%Y-%m-%d %H:%M:%S")
else:
return ""
def get_std_soft_end_date(self):
""" If the date is custom, return the soft datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
if self._soft_end != datetime.max:
- return self._soft_end.strftime("%Y-%m-%d %H:%M:%S") if self._soft_end is not None else ""
+ return self._soft_end.strftime("%Y-%m-%d %H:%M:%S")
else:
return ""
From 39ce930bee4de5597682f46602f7aa265c9f9895 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 14 Dec 2023 08:24:22 +0100
Subject: [PATCH 27/49] cleaning for PR
---
inginious/frontend/course_factory.py | 13 -------------
inginious/frontend/pages/utils.py | 2 --
inginious/frontend/plugins/contests/__init__.py | 2 +-
inginious/frontend/task_dispensers/toc.py | 2 --
inginious/frontend/task_dispensers/util.py | 2 --
.../config_items/accessibility.html | 1 -
6 files changed, 1 insertion(+), 21 deletions(-)
diff --git a/inginious/frontend/course_factory.py b/inginious/frontend/course_factory.py
index 4412b804b..4bdd202f8 100644
--- a/inginious/frontend/course_factory.py
+++ b/inginious/frontend/course_factory.py
@@ -6,7 +6,6 @@
""" Factory for loading courses from disk """
from pymongo import ReturnDocument
-from datetime import datetime
from inginious.frontend.log import get_course_logger
@@ -15,8 +14,6 @@
from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes
-
-
class CourseFactory(object):
""" Load courses from disk """
@@ -102,11 +99,6 @@ def _migrate_legacy_courses(self):
if taskset.is_legacy() and not self._database.courses.find_one({"_id": tasksetid}):
courseids.append(tasksetid)
- # look here for bad accessibilitY structure ??? -> _migrate_legacy_courses detect the courses whithout taskset (taskset id in DB and correct taskset yaml file)
- # I can maybe also checko here if the taskset file has the right structure (task accessibilities).
-
- # where to check for DB structure ?
-
for courseid in courseids:
get_course_logger(courseid).warning("Trying to migrate legacy course {}.".format(courseid))
@@ -127,13 +119,8 @@ def _migrate_legacy_courses(self):
if "registration" in taskset_descriptor:
taskset_descriptor["registration"] = change_access_structure(taskset_descriptor["registration"])
- # here transform task accessibilities ? -> no, it will be done during the migration of the taskset (import_legacy_tasks)
- # task accessibilities are not in the course descriptor, but in the tasks descriptors (task.yaml)
-
taskset_descriptor = dict_data_str_to_datetimes(taskset_descriptor)
- #cleaned_taskset_descriptor = dict_data_str_to_datetimes(cleaned_taskset_descriptor)
self._database.courses.update_one({"_id": courseid}, {"$set": taskset_descriptor}, upsert=True)
- # why not set cleaned_taskset_descriptor ? -> parce qu'on transmet le taskset_descriptor pour l'enregistrer en DB
self._taskset_factory.update_taskset_descriptor_content(courseid, cleaned_taskset_descriptor)
except TasksetNotFoundException as e:
get_course_logger(courseid).warning("No migration from taskset possible for courseid {}.".format(courseid))
diff --git a/inginious/frontend/pages/utils.py b/inginious/frontend/pages/utils.py
index 5c4007551..40684b3e5 100644
--- a/inginious/frontend/pages/utils.py
+++ b/inginious/frontend/pages/utils.py
@@ -13,7 +13,6 @@
from flask import redirect, url_for
from flask.views import MethodView
from werkzeug.exceptions import NotFound, NotAcceptable
-from datetime import datetime
from inginious.client.client import Client
from inginious.common import custom_yaml
@@ -365,4 +364,3 @@ def register_utils(database, user_manager, template_helper: TemplateHelper):
current_users, name, id, placeholder,
single)
)
-
diff --git a/inginious/frontend/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py
index 56348589f..6acad23cf 100644
--- a/inginious/frontend/plugins/contests/__init__.py
+++ b/inginious/frontend/plugins/contests/__init__.py
@@ -34,7 +34,7 @@ def __init__(self, task_list_func, dispenser_data, database, course_id):
TableOfContents.__init__(self, task_list_func, dispenser_data.get("toc_data", {}), database, course_id)
self._contest_settings = dispenser_data.get(
'contest_settings',
- {"enabled": False, # enabled false ?
+ {"enabled": False,
"start": datetime.now(),
"end": datetime.now() + timedelta(hours=1),
"blackout": 0,
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 3a2f185f6..e72859b83 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -5,7 +5,6 @@
import copy
import inginious
from collections import OrderedDict
-from datetime import datetime
from functools import reduce
from operator import concat
@@ -72,7 +71,6 @@ def get_group_submission(self, taskid):
def get_accessibilities(self, taskids, usernames):
""" Get the accessible time of this task """
-
return {username: {taskid: AccessibleTime(Accessibility.get_value(self._task_config.get(taskid, {})))
for taskid in taskids } for username in usernames}
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index f089f0da6..5f545f710 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -2,7 +2,6 @@
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.
-import copy
from datetime import datetime
from abc import ABCMeta, abstractmethod
@@ -375,7 +374,6 @@ def parse_tasks_config(task_list, config_items, data):
def check_task_config(task_list, config_items, data):
"""
-
:param data: the raw content of the task settings
:return: (True, '') if the settings are valid or (False, The error message) otherwise
"""
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index e81e3255c..8857d33fe 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -85,7 +85,6 @@
var taskid = button.data('taskid');
if (!("accessibility" in dispenser_config[taskid]))
return;
- //general_taskid = taskid
var accessibility = dispenser_config[taskid]["accessibility"];
var value;
From 0998b0b1d5fe64b26cc10cbaf5d02ecf2dc29ba6 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 4 Jan 2024 10:43:59 +0100
Subject: [PATCH 28/49] [frontend/accessibility] Fixing modal feed to all modal
instead of specific one
---
.../config_items/accessibility.html | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index 8857d33fe..e58a8ea88 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -83,6 +83,9 @@
$("#edit_task_modal").on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var taskid = button.data('taskid');
+
+ console.log(taskid);
+
if (!("accessibility" in dispenser_config[taskid]))
return;
var accessibility = dispenser_config[taskid]["accessibility"];
@@ -96,9 +99,9 @@
if (Object.values(accessibility).some(date => !isSpecialDate(date))) {
value = 'custom';
- $('#accessibility_start_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["start"]) ? '' : accessibility["start"]);
- $('#accessibility_soft_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["soft_end"]) ? '' : accessibility["soft_end"]);
- $('#accessibility_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["end"]) ? '' : accessibility["end"]);
+ $(this).find('#accessibility_start_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["start"]) ? '' : accessibility["start"]);
+ $(this).find('#accessibility_soft_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["soft_end"]) ? '' : accessibility["soft_end"]);
+ $(this).find('#accessibility_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["end"]) ? '' : accessibility["end"]);
} else if (Object.values(accessibility).some(date => date == "1-01-01 00:00:00")) {
value = 'true';
From 96b2446de46e6e9ae68deaef4e2c7c3e7d5c8a27 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Fri, 5 Jan 2024 10:28:03 +0100
Subject: [PATCH 29/49] [frontend/accessibility] Fixing datetime picker
cleaning during modal feed
Fixing previous commit
---
.../config_items/accessibility.html | 21 +++++++++++--------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index e58a8ea88..47f1c38ff 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -84,8 +84,6 @@
var button = $(event.relatedTarget);
var taskid = button.data('taskid');
- console.log(taskid);
-
if (!("accessibility" in dispenser_config[taskid]))
return;
var accessibility = dispenser_config[taskid]["accessibility"];
@@ -99,14 +97,19 @@
if (Object.values(accessibility).some(date => !isSpecialDate(date))) {
value = 'custom';
- $(this).find('#accessibility_start_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["start"]) ? '' : accessibility["start"]);
- $(this).find('#accessibility_soft_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["soft_end"]) ? '' : accessibility["soft_end"]);
- $(this).find('#accessibility_end_picker').datetimepicker('defaultDate', isSpecialDate(accessibility["end"]) ? '' : accessibility["end"]);
-
- } else if (Object.values(accessibility).some(date => date == "1-01-01 00:00:00")) {
- value = 'true';
+ $(this).find('#accessibility_start_picker').datetimepicker('date', isSpecialDate(accessibility["start"]) ? null : accessibility["start"]);
+ $(this).find('#accessibility_soft_end_picker').datetimepicker('date', isSpecialDate(accessibility["soft_end"]) ? null : accessibility["soft_end"]);
+ $(this).find('#accessibility_end_picker').datetimepicker('date', isSpecialDate(accessibility["end"]) ? null : accessibility["end"]);
} else {
- value = 'false';
+ if (Object.values(accessibility).some(date => date == "1-01-01 00:00:00")) {
+ value = 'true';
+ } else {
+ value = 'false';
+ }
+
+ $(this).find('#accessibility_start_picker').datetimepicker('date', null);
+ $(this).find('#accessibility_soft_end_picker').datetimepicker('date', null);
+ $(this).find('#accessibility_end_picker').datetimepicker('date', null);
}
var field = $(this).find(".accessibility input[value=" + value + "]");
From d25e5a0b3961cd094028d2c5272f2e4a049dc5bf Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 25 Jan 2024 16:03:32 +0100
Subject: [PATCH 30/49] [frontend/contest] Fixing constest plugin with new
database date format
+ course_factory method used on taskset_factory
---
inginious/frontend/plugins/contests/__init__.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/inginious/frontend/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py
index 6acad23cf..eb1e5fa0c 100644
--- a/inginious/frontend/plugins/contests/__init__.py
+++ b/inginious/frontend/plugins/contests/__init__.py
@@ -83,8 +83,8 @@ def course_menu(course, template_helper):
contest_data = task_dispenser.get_contest_data()
if contest_data['enabled']:
- start = datetime.strptime(contest_data['start'], "%Y-%m-%d %H:%M:%S")
- end = datetime.strptime(contest_data['end'], "%Y-%m-%d %H:%M:%S")
+ start = contest_data['start']
+ end = contest_data['end']
blackout = end - timedelta(hours=contest_data['blackout'])
return template_helper.render("course_menu.html", template_folder="frontend/plugins/contests",
course=course, start=start, end=end, blackout=blackout)
@@ -96,15 +96,15 @@ class ContestScoreboard(INGIniousAuthPage):
""" Displays the scoreboard of the contest """
def GET_AUTH(self, courseid): # pylint: disable=arguments-differ
- course = self.taskset_factory.get_course(courseid)
+ course = self.course_factory.get_course(courseid)
task_dispenser = course.get_task_dispenser()
if not task_dispenser.get_id() == Contest.get_id():
raise NotFound()
contest_data = task_dispenser.get_contest_data()
if not contest_data['enabled']:
raise NotFound()
- start = datetime.strptime(contest_data['start'], "%Y-%m-%d %H:%M:%S")
- end = datetime.strptime(contest_data['end'], "%Y-%m-%d %H:%M:%S")
+ start = contest_data['start']
+ end = contest_data['end']
blackout = end - timedelta(hours=contest_data['blackout'])
users = self.user_manager.get_course_registered_users(course)
From ea4df4e2926fc7408b628a8db4a96d7d5b386daf Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Fri, 26 Jan 2024 14:21:13 +0100
Subject: [PATCH 31/49] [frontend/constest] Fixing no soft_end given in contest
.get_accessibility()
---
inginious/frontend/plugins/contests/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inginious/frontend/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py
index eb1e5fa0c..ef21770f9 100644
--- a/inginious/frontend/plugins/contests/__init__.py
+++ b/inginious/frontend/plugins/contests/__init__.py
@@ -57,7 +57,7 @@ def check_dispenser_data(self, dispenser_data):
def get_accessibilities(self, taskids, usernames): # pylint: disable=unused-argument
contest_data = self.get_contest_data()
if contest_data['enabled']:
- accessibility = {"start": contest_data['start'], "end": contest_data['end']}
+ accessibility = {"start": contest_data['start'], "soft_end": contest_data['end'], "end": contest_data['end']}
return {username: {taskid: AccessibleTime(accessibility) for taskid in taskids} for username in usernames}
else:
return TableOfContents.get_accessibilities(self, taskids, usernames)
From c6685c32dce58cfec66163b7252a3e772a1f2939 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 16 Apr 2024 10:37:51 +0200
Subject: [PATCH 32/49] [frontend] Indicating a four digit year when
transforming datetimes into strings
Had years with less than 4 digits not transformed into strings with years without leading "0" characters.
---
inginious/frontend/accessible_time.py | 12 ++++++------
inginious/frontend/pages/course_admin/danger_zone.py | 4 ++--
inginious/frontend/pages/course_admin/statistics.py | 6 +++---
inginious/frontend/pages/tasks.py | 2 +-
inginious/frontend/plugins/contests/course_menu.html | 6 +++---
inginious/frontend/plugins/contests/scoreboard.html | 6 +++---
inginious/frontend/util.py | 2 +-
7 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 597380ef3..6c78c30fe 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -111,23 +111,23 @@ def is_never_accessible(self):
return self._start == datetime.max and self._end == datetime.max
def get_std_start_date(self):
- """ If the date is custom, return the start datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
+ """ If the date is custom, return the start datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._start != datetime.min and self._start != datetime.max:
- return self._start.strftime("%Y-%m-%d %H:%M:%S")
+ return self._start.strftime("%4Y-%m-%d %H:%M:%S")
else:
return ""
def get_std_end_date(self):
- """ If the date is custom, return the end datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
+ """ If the date is custom, return the end datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._end != datetime.max:
- return self._end.strftime("%Y-%m-%d %H:%M:%S")
+ return self._end.strftime("%4Y-%m-%d %H:%M:%S")
else:
return ""
def get_std_soft_end_date(self):
- """ If the date is custom, return the soft datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
+ """ If the date is custom, return the soft datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._soft_end != datetime.max:
- return self._soft_end.strftime("%Y-%m-%d %H:%M:%S")
+ return self._soft_end.strftime("%4Y-%m-%d %H:%M:%S")
else:
return ""
diff --git a/inginious/frontend/pages/course_admin/danger_zone.py b/inginious/frontend/pages/course_admin/danger_zone.py
index 51b63dcb6..99bfb8d97 100644
--- a/inginious/frontend/pages/course_admin/danger_zone.py
+++ b/inginious/frontend/pages/course_admin/danger_zone.py
@@ -182,7 +182,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
try:
dt = datetime.datetime.strptime(data["backupdate"], "%Y%m%d.%H%M%S")
self.restore_course(courseid, data["backupdate"])
- msg = _("Course restored to date : {}.").format(dt.strftime("%Y-%m-%d %H:%M:%S"))
+ msg = _("Course restored to date : {}.").format(dt.strftime("%4Y-%m-%d %H:%M:%S"))
except Exception as ex:
msg = _("An error occurred while restoring backup: {}").format(repr(ex))
error = True
@@ -208,7 +208,7 @@ def get_backup_list(self, course):
for backup in glob.glob(os.path.join(filepath, '*.zip')):
try:
basename = os.path.basename(backup)[0:-4]
- dt = datetime.datetime.strptime(basename, "%Y%m%d.%H%M%S").strftime("%Y-%m-%d %H:%M:%S")
+ dt = datetime.datetime.strptime(basename, "%Y%m%d.%H%M%S").strftime("%4Y-%m-%d %H:%M:%S")
backups.append({"file": basename, "date": dt})
except: # Wrong format
pass
diff --git a/inginious/frontend/pages/course_admin/statistics.py b/inginious/frontend/pages/course_admin/statistics.py
index dd9e8e68b..43b9841ef 100644
--- a/inginious/frontend/pages/course_admin/statistics.py
+++ b/inginious/frontend/pages/course_admin/statistics.py
@@ -195,8 +195,8 @@ def page(self, course, params):
now = datetime.now().replace(minute=0, second=0, microsecond=0)
daterange = [now - timedelta(days=14), now]
- params["date_before"] = daterange[1].strftime("%Y-%m-%d %H:%M:%S")
- params["date_after"] = daterange[0].strftime("%Y-%m-%d %H:%M:%S")
+ params["date_before"] = daterange[1].strftime("%4Y-%m-%d %H:%M:%S")
+ params["date_after"] = daterange[0].strftime("%4Y-%m-%d %H:%M:%S")
display_hours = (daterange[1] - daterange[0]).days < 4
users, tutored_users, audiences, tutored_audiences, tasks, limit = self.get_course_params(course, params)
@@ -209,7 +209,7 @@ def page(self, course, params):
float(params["grade_min"]) if params.get('grade_min', '') else None,
float(params["grade_max"]) if params.get('grade_max', '') else None
],
- submit_time_between=[x.strftime("%Y-%m-%d %H:%M:%S") for x in daterange],
+ submit_time_between=[x.strftime("%4Y-%m-%d %H:%M:%S") for x in daterange],
keep_only_crashes="crashes_only" in params)
stats_tasks = self._tasks_stats(tasks, filter, limit)
diff --git a/inginious/frontend/pages/tasks.py b/inginious/frontend/pages/tasks.py
index 6515465c7..85d68dfd3 100644
--- a/inginious/frontend/pages/tasks.py
+++ b/inginious/frontend/pages/tasks.py
@@ -341,7 +341,7 @@ def submission_to_json(self, task, data, debug, reloading=False, replace=False,
tojson["title"] = _("An internal error occurred. Please retry later. "
"If the error persists, send an email to the course administrator.")
- tojson["title"] += " " + _("[Submission #{submissionid} ({submissionDate})]").format(submissionid=data["_id"], submissionDate=data["submitted_on"].strftime("%Y-%m-%d %H:%M:%S"))
+ tojson["title"] += " " + _("[Submission #{submissionid} ({submissionDate})]").format(submissionid=data["_id"], submissionDate=data["submitted_on"].strftime("%4Y-%m-%d %H:%M:%S"))
tojson["title"] = self.plugin_manager.call_hook_recursive("feedback_title", task=task, submission=data, title=tojson["title"])["title"]
tojson["text"] = data.get("text", "")
diff --git a/inginious/frontend/plugins/contests/course_menu.html b/inginious/frontend/plugins/contests/course_menu.html
index e7e08bd85..d9b186b71 100644
--- a/inginious/frontend/plugins/contests/course_menu.html
+++ b/inginious/frontend/plugins/contests/course_menu.html
@@ -21,11 +21,11 @@
Contest
{% if start > start.now() %}
-
+
{% else %}
-
+
{% if blackout != end %}
-
+
{% endif %}
{% endif %}
diff --git a/inginious/frontend/plugins/contests/scoreboard.html b/inginious/frontend/plugins/contests/scoreboard.html
index 9610502c7..340cf3d1d 100644
--- a/inginious/frontend/plugins/contests/scoreboard.html
+++ b/inginious/frontend/plugins/contests/scoreboard.html
@@ -32,11 +32,11 @@ {{course.get_name(user_manager.session_language())}} - Scoreboard
{% if start > start.now() %}
-
+
{% else %}
-
+
{% if blackout != end %}
-
+
{% endif %}
{% endif %}
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
index bc34264fa..ae2be35b4 100644
--- a/inginious/frontend/util.py
+++ b/inginious/frontend/util.py
@@ -11,7 +11,7 @@ def dict_data_datetimes_to_str(data):
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, datetime):
- data[key] = value.strftime("%Y-%m-%d %H:%M:%S")
+ data[key] = value.strftime("%4Y-%m-%d %H:%M:%S")
else:
dict_data_datetimes_to_str(value)
elif isinstance(data, list):
From 9b5416d031ff6ad8541c68fbf8bce4fe3a99f77a Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 16 Apr 2024 13:43:31 +0200
Subject: [PATCH 33/49] [custom_yaml] refactoring formatting for datetime yaml
representer
Simplifying 4 digit year check + removing empty string check because can only be applied to datetime objects
---
inginious/common/custom_yaml.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/inginious/common/custom_yaml.py b/inginious/common/custom_yaml.py
index 0cc2a97c4..be1609fa3 100644
--- a/inginious/common/custom_yaml.py
+++ b/inginious/common/custom_yaml.py
@@ -77,10 +77,8 @@ def _default_representer(dumper, data):
return _long_str_representer(dumper, str(data))
def _timestamp_representer(dumper, data):
- formatted_date = data.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if data != "" else None
- # Ensure the year, month and day are always correctly formatted
- formatted_date = formatted_date.replace(data.strftime("%Y"), "{:04d}".format(data.year), 1)
- return dumper.represent_scalar('tag:yaml.org,2002:timestamp', formatted_date)
+ date = data.strftime("%4Y-%m-%dT%H:%M:%S.%fZ")
+ return dumper.represent_scalar('tag:yaml.org,2002:timestamp', date)
OrderedDumper.add_representer(str, _long_str_representer)
From 9395f96c4713ac31d9bfd74fc6406d5e2a86ed1a Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 17 Apr 2024 12:10:47 +0200
Subject: [PATCH 34/49] [frontend] Remove min and max hardcoded dates + storing
without microseconds
Storing as min and max attributes of AccessibleTime when possible. And using dates without microseconds
---
inginious/common/custom_yaml.py | 2 +-
inginious/frontend/accessible_time.py | 52 +++++++++------
inginious/frontend/courses.py | 4 +-
.../frontend/pages/course_admin/settings.py | 24 +++----
inginious/frontend/pages/marketplace.py | 2 +-
inginious/frontend/pages/tasksets.py | 4 +-
.../task_dispensers/combinatory_test.py | 2 +-
inginious/frontend/task_dispensers/util.py | 2 +-
.../config_items/accessibility.html | 66 +++++++++----------
inginious/frontend/util.py | 4 +-
10 files changed, 86 insertions(+), 76 deletions(-)
diff --git a/inginious/common/custom_yaml.py b/inginious/common/custom_yaml.py
index be1609fa3..ff3f64440 100644
--- a/inginious/common/custom_yaml.py
+++ b/inginious/common/custom_yaml.py
@@ -77,7 +77,7 @@ def _default_representer(dumper, data):
return _long_str_representer(dumper, str(data))
def _timestamp_representer(dumper, data):
- date = data.strftime("%4Y-%m-%dT%H:%M:%S.%fZ")
+ date = data.strftime("%4Y-%m-%dT%H:%M:%SZ")
return dumper.represent_scalar('tag:yaml.org,2002:timestamp', date)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 6c78c30fe..067ebcaeb 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -9,20 +9,25 @@
def parse_date(date, default=None):
- """ Parse a valid date """
- if date == "" or not isinstance(date, str):
+ """
+ Parse a valid date
+ :param date: string, date to parse
+ :param default: datetime object, optionnal, default value to return if date is empty
+ :return: datetime object of the parsed date
+ """
+ if date == "":
if default is not None:
return default
else:
- raise Exception("Unknown format for " + date)
+ raise Exception("Empty date given to AccessibleTime")
- if date == "1-01-01 00:00:00":
+ if date == "0001-01-01 00:00:00":
return datetime.min
if date == "9999-12-31 23:59:59":
- return datetime.max
+ return datetime.max.replace(microsecond=0)
- for format_type in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H", "%Y-%m-%d", "%d/%m/%Y %H:%M:%S", "%d/%m/%Y %H:%M", "%d/%m/%Y %H",
- "%d/%m/%Y"]:
+ for format_type in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H", "%Y-%m-%d", "%d/%m/%Y %H:%M:%S",
+ "%d/%m/%Y %H:%M", "%d/%m/%Y %H", "%d/%m/%Y"]:
try:
return datetime.strptime(date, format_type)
except ValueError:
@@ -41,26 +46,31 @@ def __init__(self, period):
:param period : dict, contains start, end and optionally soft_end as datetime objects or strings
"""
+ self.max = datetime.max.replace(microsecond=0)
+ self.min = datetime.min
+
if not isinstance(period, dict):
raise Exception("Wrong period given to AccessibleTime")
# transforming strings into datetimes in case AccessibleTime is used in html files, where datetime objects are not supported
for key, date in period.items():
- if isinstance(date, str) and date != "":
- period[key] = parse_date(date)
- elif not isinstance(date, (datetime, str)):
+ if not isinstance(date, (datetime, str)):
raise Exception("Wrong period given to AccessibleTime")
- elif date == "":
- raise Exception("Empty date given to AccessibleTime")
+ elif isinstance(date, str):
+ period[key] = parse_date(date)
- self._start = self.adapt_database_date(period["start"])
- self._end = self.adapt_database_date(period["end"])
+ #self._start = self.adapt_database_date(period["start"])
+ #self._end = self.adapt_database_date(period["end"])
+ self._start = period["start"]
+ self._end = period["end"]
if "soft_end" in period:
- self._soft_end = self.adapt_database_date(period["soft_end"])
+ #self._soft_end = self.adapt_database_date(period["soft_end"])
+ self._soft_end = period["soft_end"]
if self._soft_end > self._end:
self._soft_end = self._end
+ # TO REMOVE / CHANGE -> We want the max and min to be stored without milli or microseconds
def adapt_database_date(self, date):
"""
Check if the date is the max or min DB value and transforms it into a datetime object.
@@ -72,7 +82,7 @@ def adapt_database_date(self, date):
if date == datetime(1, 1, 1, 0, 0, 0, 000000):
return datetime.min
elif date == datetime(9999, 12, 31, 23, 59, 59, 999000):
- return datetime.max
+ return datetime.max.replace(microsecond=0)
else:
return date
@@ -104,29 +114,29 @@ def is_open_with_soft_deadline(self, when=None):
def is_always_accessible(self):
""" Returns true if the course/task is always accessible """
- return self._start == datetime.min and self._end == datetime.max
+ return self._start == self.min and self._end == self.max
def is_never_accessible(self):
""" Returns true if the course/task is never accessible """
- return self._start == datetime.max and self._end == datetime.max
+ return self._start == self.max and self._end == self.max
def get_std_start_date(self):
""" If the date is custom, return the start datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
- if self._start != datetime.min and self._start != datetime.max:
+ if self._start != self.min and self._start != self.max:
return self._start.strftime("%4Y-%m-%d %H:%M:%S")
else:
return ""
def get_std_end_date(self):
""" If the date is custom, return the end datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
- if self._end != datetime.max:
+ if self._end != self.max:
return self._end.strftime("%4Y-%m-%d %H:%M:%S")
else:
return ""
def get_std_soft_end_date(self):
""" If the date is custom, return the soft datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
- if self._soft_end != datetime.max:
+ if self._soft_end != self.max:
return self._soft_end.strftime("%4Y-%m-%d %H:%M:%S")
else:
return ""
diff --git a/inginious/frontend/courses.py b/inginious/frontend/courses.py
index 1417e0942..1dcb2fc16 100644
--- a/inginious/frontend/courses.py
+++ b/inginious/frontend/courses.py
@@ -75,8 +75,8 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
# Force some parameters if LTI is active
if self.is_lti():
- self._accessible = AccessibleTime({"start": datetime.min, "end": datetime.max})
- self._registration = AccessibleTime({"start": datetime.max, "end": datetime.max})
+ self._accessible = AccessibleTime({"start": datetime.min, "end": datetime.max.replace(microsecond=0)})
+ self._registration = AccessibleTime({"start": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)})
self._registration_password = None
self._registration_ac = None
self._registration_ac_list = []
diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py
index 5558f79ca..8ef71ffad 100644
--- a/inginious/frontend/pages/course_admin/settings.py
+++ b/inginious/frontend/pages/course_admin/settings.py
@@ -41,14 +41,14 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['groups_student_choice'] = True if data["groups_student_choice"] == "true" else False
if data["accessible"] == "custom":
- course_content['accessible']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else datetime.min
- course_content['accessible']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else datetime.max
+ course_content['accessible']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else course._accessible.min
+ course_content['accessible']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else course._accessible.max
elif data["accessible"] == "true":
- course_content['accessible']["start"] = datetime.min
- course_content['accessible']["end"] = datetime.max
+ course_content['accessible']["start"] = course._accessible.min
+ course_content['accessible']["end"] = course._accessible.max
else:
- course_content['accessible']["start"] = datetime.max
- course_content['accessible']["end"] = datetime.max
+ course_content['accessible']["start"] = course._accessible.max
+ course_content['accessible']["end"] = course._accessible.max
try:
AccessibleTime(course_content['accessible'])
@@ -59,14 +59,14 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['allow_preview'] = True if data["allow_preview"] == "true" else False
if data["registration"] == "custom":
- course_content['registration']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else datetime.min
- course_content['registration']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else datetime.max
+ course_content['registration']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else course._accessible.min
+ course_content['registration']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else course._accessible.max
elif data["registration"] == "true":
- course_content['registration']["start"] = datetime.min
- course_content['registration']["end"] = datetime.max
+ course_content['registration']["start"] = course._accessible.min
+ course_content['registration']["end"] = course._accessible.max
else:
- course_content['registration']["start"] = datetime.max
- course_content['registration']["end"] = datetime.max
+ course_content['registration']["start"] = course._accessible.max
+ course_content['registration']["end"] = course._accessible.max
try:
AccessibleTime(course_content['registration'])
diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py
index 4c6557d9c..d9ba6b3ac 100644
--- a/inginious/frontend/pages/marketplace.py
+++ b/inginious/frontend/pages/marketplace.py
@@ -83,7 +83,7 @@ def import_taskset(taskset, new_tasksetid, username, taskset_factory):
try:
new_descriptor = {"description": old_descriptor.get("description", ""),
'admins': [username],
- "accessible": {"start": datetime.max, "end": datetime.max},
+ "accessible": {"start": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)},
"tags": old_descriptor.get("tags", {})}
if "name" in old_descriptor:
new_descriptor["name"] = old_descriptor["name"] + " - " + new_tasksetid
diff --git a/inginious/frontend/pages/tasksets.py b/inginious/frontend/pages/tasksets.py
index 30c18e619..a4d6b40d7 100644
--- a/inginious/frontend/pages/tasksets.py
+++ b/inginious/frontend/pages/tasksets.py
@@ -36,8 +36,8 @@ def POST_AUTH(self): # pylint: disable=arguments-differ
if self.user_manager.session_username() in taskset.get_admins() or taskset.is_public() or self.user_manager.user_is_superadmin():
task_dispenser = taskset.get_task_dispenser()
self.course_factory.create_course(courseid, {
- "name": courseid, "accessible": {"start": datetime.max, "end": datetime.max},
- "registration": {"start": datetime.max, "end": datetime.max}, "tasksetid": taskset.get_id(),
+ "name": courseid, "accessible": {"start": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)},
+ "registration": {"start": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)}, "tasksetid": taskset.get_id(),
"admins": [self.user_manager.session_username()], "students": [],
"task_dispenser": task_dispenser.get_id(), "dispenser_data": task_dispenser.get_dispenser_data()
})
diff --git a/inginious/frontend/task_dispensers/combinatory_test.py b/inginious/frontend/task_dispensers/combinatory_test.py
index c6c046c4a..4c587fbcb 100644
--- a/inginious/frontend/task_dispensers/combinatory_test.py
+++ b/inginious/frontend/task_dispensers/combinatory_test.py
@@ -31,7 +31,7 @@ def get_group_submission(self, taskid):
return False
def get_accessibilities(self, taskids, usernames):
- result = {username: {taskid: AccessibleTime({"start": datetime.min, "soft_end": datetime.max, "end": datetime.max})
+ result = {username: {taskid: AccessibleTime({"start": datetime.min, "soft_end": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)})
for taskid in taskids} for username in usernames}
for index, section in enumerate(self._toc):
task_list = [taskid for taskid in section.get_tasks()
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index 5f545f710..9174a53d5 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -180,7 +180,7 @@ def get_value(cls, task_config):
class Accessibility(TaskConfigItem):
- default = dict({"start": datetime.min, "soft_end": datetime.max, "end": datetime.max})
+ default = dict({"start": datetime.min, "soft_end": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)})
@classmethod
def get_template(cls):
diff --git a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
index 47f1c38ff..a661bbe05 100644
--- a/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
+++ b/inginious/frontend/templates/task_dispensers_admin/config_items/accessibility.html
@@ -1,8 +1,8 @@
{# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for #}
{# more information about the licensing of this file. #}
+{% set at = element.get_task_dispenser().get_accessibility(taskid, user_manager.session_username()) %}
{% if short %}
- {% set at = element.get_task_dispenser().get_accessibility(taskid, user_manager.session_username()) %}
{{_("Accessibility")}} :
{{_("Never")}}
{{_("Always")}}
@@ -89,7 +89,7 @@
var accessibility = dispenser_config[taskid]["accessibility"];
var value;
- special_dates = ["1-01-01 00:00:00", "9999-12-31 23:59:59"]
+ special_dates = ["{{ at.min }}", "{{ at.max }}"]
function isSpecialDate(date) {
return special_dates.includes(date);
}
@@ -101,7 +101,7 @@
$(this).find('#accessibility_soft_end_picker').datetimepicker('date', isSpecialDate(accessibility["soft_end"]) ? null : accessibility["soft_end"]);
$(this).find('#accessibility_end_picker').datetimepicker('date', isSpecialDate(accessibility["end"]) ? null : accessibility["end"]);
} else {
- if (Object.values(accessibility).some(date => date == "1-01-01 00:00:00")) {
+ if (Object.values(accessibility).some(date => date == "{{ at.min }}")) {
value = 'true';
} else {
value = 'false';
@@ -126,36 +126,36 @@
$("#task_" + taskid + " .accessibility").hide();
if($(this).val() == "true") {
$("#task_" + taskid + " .accessibility-always").show();
- dispenser_config[taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
- dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["start"] = "{{ at.min }}";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "{{ at.max }}";
+ dispenser_config[taskid]["accessibility"]["end"] = "{{ at.max }}";
} else if($(this).val() == "false") {
$("#task_" + taskid + " .accessibility-never").show();
- dispenser_config[taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
- dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["start"] = "{{ at.max }}";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "{{ at.max }}";
+ dispenser_config[taskid]["accessibility"]["end"] = "{{ at.max }}";
} else {
$("#task_" + taskid + " .accessibility-custom").show();
var start = $("#edit_task_modal").find("#accessibility_start").val();
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
- dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
- dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "{{ at.min }}" : start;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "{{ at.max }}" : soft_end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "{{ at.max }}" : end;
}
} else {
let id = $(this).attr("id");
if (id == "accessibility_start") {
var start = $("#task_" + taskid + " .accessibility-custom-start").text($(this).val());
- dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "{{ at.min }}" : start;
} else if(id == "accessibility_end") {
var end = $("#task_" + taskid + " .accessibility-custom-end").text($(this).val());
- dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "{{ at.max }}" : end;
} else {
var soft_end = $("#task_" + taskid + " .accessibility-custom-soft-end").text($(this).val());
- dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "{{ at.max }}" : soft_end;
}
}
});
@@ -167,7 +167,7 @@
let val = $("#edit_task_modal #accessibility_start").val();
$("#task_" + taskid + " .accessibility-custom-start").text(val);
var start = $("#edit_task_modal").find("#accessibility_start").val();
- dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "{{ at.min }}" : start;
});
$('#accessibility_end_picker').on("change.datetimepicker", function () {
@@ -177,7 +177,7 @@
let val = $("#edit_task_modal #accessibility_end").val();
$("#task_" + taskid + " .accessibility-custom-end").text(val);
var end = $("#edit_task_modal").find("#accessibility_end").val();
- dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "{{ at.max }}" : end;
});
$('#accessibility_soft_end_picker').on("change.datetimepicker", function () {
@@ -187,7 +187,7 @@
let val = $("#edit_task_modal #accessibility_soft_end").val();
$("#task_" + taskid + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#edit_task_modal").find("#accessibility_soft_end").val();
- dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "{{ at.max }}" : soft_end;
});
// Catch grouped action changes
@@ -202,23 +202,23 @@
$("#task_" + taskid + " .accessibility").hide();
if(value == "true") {
$("#task_" + taskid + " .accessibility-always").show();
- dispenser_config[taskid]["accessibility"]["start"] = "1-01-01 00:00:00";
- dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["start"] = "{{ at.min }}";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "{{ at.max }}";
+ dispenser_config[taskid]["accessibility"]["end"] = "{{ at.max }}";
} else if(value== "false") {
$("#task_" + taskid + " .accessibility-never").show();
- dispenser_config[taskid]["accessibility"]["start"] = "9999-12-31 23:59:59";
- dispenser_config[taskid]["accessibility"]["soft_end"] = "9999-12-31 23:59:59";
- dispenser_config[taskid]["accessibility"]["end"] = "9999-12-31 23:59:59";
+ dispenser_config[taskid]["accessibility"]["start"] = "{{ at.max }}";
+ dispenser_config[taskid]["accessibility"]["soft_end"] = "{{ at.max }}";
+ dispenser_config[taskid]["accessibility"]["end"] = "{{ at.max }}";
} else {
$("#task_" + taskid + " .accessibility-custom").show();
var start = $("#grouped-actions-edit #accessibility_start").val();
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
- dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
- dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "{{ at.min }}" : start;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "{{ at.max }}" : soft_end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "{{ at.max }}" : end;
}
} else {
if (id == "accessibility_start")
@@ -232,9 +232,9 @@
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
- dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
- dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[taskid]["accessibility"]["start"] = start == "" ? "{{ at.min }}" : start;
+ dispenser_config[taskid]["accessibility"]["soft_end"] = soft_end == "" ? "{{ at.max }}" : soft_end;
+ dispenser_config[taskid]["accessibility"]["end"] = end == "" ? "{{ at.max }}" : end;
}
});
});
@@ -244,7 +244,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-start").text(val);
var start = $("#grouped-actions-edit #accessibility_start").val();
- dispenser_config[$(this).data("taskid")]["accessibility"]["start"] = start == "" ? "1-01-01 00:00:00" : start;
+ dispenser_config[$(this).data("taskid")]["accessibility"]["start"] = start == "" ? "{{ at.min }}" : start;
});
});
$('#accessibility_end_picker_grouped').on("change.datetimepicker", function () {
@@ -252,7 +252,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-end").text(val);
var end = $("#grouped-actions-edit #accessibility_end").val();
- dispenser_config[$(this).data("taskid")]["accessibility"]["end"] = end == "" ? "9999-12-31 23:59:59" : end;
+ dispenser_config[$(this).data("taskid")]["accessibility"]["end"] = end == "" ? "{{ at.max }}" : end;
});
});
$('#accessibility_soft_end_picker_grouped').on("change.datetimepicker", function () {
@@ -260,7 +260,7 @@
$(".grouped-actions-task:checked").each(function () {
$("#task_" + $(this).data("taskid") + " .accessibility-custom-soft-end").text(val);
var soft_end = $("#grouped-actions-edit #accessibility_soft_end").val();
- dispenser_config[$(this).data("taskid")]["accessibility"]["soft_end"] = soft_end == "" ? "9999-12-31 23:59:59" : soft_end;
+ dispenser_config[$(this).data("taskid")]["accessibility"]["soft_end"] = soft_end == "" ? "{{ at.max }}" : soft_end;
});
});
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
index ae2be35b4..9cf9df8ff 100644
--- a/inginious/frontend/util.py
+++ b/inginious/frontend/util.py
@@ -25,10 +25,10 @@ def dict_data_str_to_datetimes(data):
for key, value in data.items():
if isinstance(value, str):
try:
- if value == "1-01-01 00:00:00":
+ if value == "0001-01-01 00:00:00":
data[key] = datetime.min
elif value == "9999-12-31 23:59:59":
- data[key] = datetime.max
+ data[key] = datetime.max.replace(microsecond=0)
else:
data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
except ValueError:
From 26867c7ac37283df6e6694c6c7d5ad9b5d7e3186 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Wed, 17 Apr 2024 13:46:44 +0200
Subject: [PATCH 35/49] [frontend/accessibleTime] Remove method adapting
database time
---
inginious/frontend/accessible_time.py | 20 --------------------
1 file changed, 20 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 067ebcaeb..1aaf25721 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -59,34 +59,14 @@ def __init__(self, period):
elif isinstance(date, str):
period[key] = parse_date(date)
- #self._start = self.adapt_database_date(period["start"])
- #self._end = self.adapt_database_date(period["end"])
self._start = period["start"]
self._end = period["end"]
if "soft_end" in period:
- #self._soft_end = self.adapt_database_date(period["soft_end"])
self._soft_end = period["soft_end"]
if self._soft_end > self._end:
self._soft_end = self._end
- # TO REMOVE / CHANGE -> We want the max and min to be stored without milli or microseconds
- def adapt_database_date(self, date):
- """
- Check if the date is the max or min DB value and transforms it into a datetime object.
- MongoDB stores ISODate() objects in the database. When we store datetime.min or datetime.max in the database,
- we will not get the same value back. It is because ISODate() objects store the date with a precision of
- milliseconds, not nanoseconds like datetime objects.
- :param date: datetime object coming from the database
- """
- if date == datetime(1, 1, 1, 0, 0, 0, 000000):
- return datetime.min
- elif date == datetime(9999, 12, 31, 23, 59, 59, 999000):
- return datetime.max.replace(microsecond=0)
- else:
- return date
-
-
def before_start(self, when=None):
""" Returns True if the task/course is not yet accessible """
if when is None:
From 085fef495f7c4da7374c099314cf5533c3d60179 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 18 Apr 2024 09:34:56 +0200
Subject: [PATCH 36/49] [frontaned/accessible_time] Adding support for legacy
time format
---
inginious/frontend/accessible_time.py | 19 +++++++++++++++----
inginious/frontend/task_dispensers/util.py | 4 ++--
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 1aaf25721..3851cf3d1 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -41,17 +41,28 @@ class AccessibleTime(object):
def __init__(self, period):
"""
Used to represent the period of time when a course/task is accessible.
- :param val : bool, optionnal, if False, it is never accessible, if True, it is always accessible or limited
- by period dict
- :param period : dict, contains start, end and optionally soft_end as datetime objects or strings
+ :param period : dict, contains start, end and optionally soft_end as datetime objects or strings.
+ Can be a string if using the legacy format "start/soft_end/end"
"""
self.max = datetime.max.replace(microsecond=0)
self.min = datetime.min
- if not isinstance(period, dict):
+ if not isinstance(period, (dict, str)):
raise Exception("Wrong period given to AccessibleTime")
+ # if legacy format
+ if isinstance(period, str):
+ period = period.split("/")
+ if len(period) == 3:
+ period = {"start": period[0], "soft_end": period[1], "end": period[2]}
+ elif len(period) == 2:
+ period = {"start": period[0], "soft_end": period[1], "end": period[1]}
+ elif len(period) == 1:
+ period = {"start": period[0], "soft_end": self.max, "end": self.max}
+ else:
+ period = {"start": self.min, "soft_end": self.max, "end": self.max}
+
# transforming strings into datetimes in case AccessibleTime is used in html files, where datetime objects are not supported
for key, date in period.items():
if not isinstance(date, (datetime, str)):
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index 9174a53d5..6297139b9 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -155,7 +155,7 @@ def get_value(cls, task_config):
class SubmissionLimit(TaskConfigItem):
- default = dict({"amount": -1, "period": -1})
+ default = {"amount": -1, "period": -1}
@classmethod
def get_template(cls):
@@ -180,7 +180,7 @@ def get_value(cls, task_config):
class Accessibility(TaskConfigItem):
- default = dict({"start": datetime.min, "soft_end": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)})
+ default = {"start": datetime.min, "soft_end": datetime.max.replace(microsecond=0), "end": datetime.max.replace(microsecond=0)}
@classmethod
def get_template(cls):
From f1a3f4c9ebe3fd2ccfe65c7cbf3c7cbf6003e58c Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Fri, 26 Apr 2024 15:24:55 +0200
Subject: [PATCH 37/49] [frontend/accessible_time] Fixing legacy structure
support
by adding boolean, empty string and None values
---
inginious/frontend/accessible_time.py | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 3851cf3d1..48c1bd3a6 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -41,19 +41,20 @@ class AccessibleTime(object):
def __init__(self, period):
"""
Used to represent the period of time when a course/task is accessible.
- :param period : dict, contains start, end and optionally soft_end as datetime objects or strings.
- Can be a string if using the legacy format "start/soft_end/end"
+ :param period : dict, contains start, end and optionally soft_end as datetime objects or strings
+ (for frontend use through templates).
+ Can be a boolean, None or string if using the legacy format "start/soft_end/end"
"""
self.max = datetime.max.replace(microsecond=0)
self.min = datetime.min
- if not isinstance(period, (dict, str)):
+ if not isinstance(period, (dict, str, bool)):
raise Exception("Wrong period given to AccessibleTime")
# if legacy format
if isinstance(period, str):
- period = period.split("/")
+ period = list(filter(lambda c: c, period.split("/"))) # splits and removes empty strings
if len(period) == 3:
period = {"start": period[0], "soft_end": period[1], "end": period[2]}
elif len(period) == 2:
@@ -63,6 +64,13 @@ def __init__(self, period):
else:
period = {"start": self.min, "soft_end": self.max, "end": self.max}
+ # add support for boolean old accessibility structure -> check how it worked before. Not even sure have to check for strings with less than two dates
+ if isinstance(period, bool):
+ if period is (True or None) or period == "":
+ period = {"start": self.min, "soft_end": self.max, "end": self.max}
+ else:
+ period = {"start": self.max, "soft_end": self.max, "end": self.max}
+
# transforming strings into datetimes in case AccessibleTime is used in html files, where datetime objects are not supported
for key, date in period.items():
if not isinstance(date, (datetime, str)):
From 96b0d45e1b6f79a85f423cd53bc719b988fcc6cd Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Mon, 29 Apr 2024 11:23:01 +0200
Subject: [PATCH 38/49] [frontend/accessible_time] Adapting course
accessibility when no registration date
+ fix None value and empty string
---
inginious/frontend/accessible_time.py | 10 ++++------
inginious/frontend/courses.py | 4 ++--
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 48c1bd3a6..4d01521c2 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -49,10 +49,10 @@ def __init__(self, period):
self.max = datetime.max.replace(microsecond=0)
self.min = datetime.min
- if not isinstance(period, (dict, str, bool)):
+ if not isinstance(period, (dict, str, bool, type(None))): # add None check
raise Exception("Wrong period given to AccessibleTime")
- # if legacy format
+ # if legacy format ( start/soft_end/end string, empty string, bool)
if isinstance(period, str):
period = list(filter(lambda c: c, period.split("/"))) # splits and removes empty strings
if len(period) == 3:
@@ -63,10 +63,8 @@ def __init__(self, period):
period = {"start": period[0], "soft_end": self.max, "end": self.max}
else:
period = {"start": self.min, "soft_end": self.max, "end": self.max}
-
- # add support for boolean old accessibility structure -> check how it worked before. Not even sure have to check for strings with less than two dates
- if isinstance(period, bool):
- if period is (True or None) or period == "":
+ if isinstance(period, (bool, type(None))):
+ if period is (True or None):
period = {"start": self.min, "soft_end": self.max, "end": self.max}
else:
period = {"start": self.max, "soft_end": self.max, "end": self.max}
diff --git a/inginious/frontend/courses.py b/inginious/frontend/courses.py
index 1dcb2fc16..703b4dabb 100644
--- a/inginious/frontend/courses.py
+++ b/inginious/frontend/courses.py
@@ -42,8 +42,8 @@ def __init__(self, courseid, content, taskset_factory, task_factory, plugin_mana
self._admins = self._content.get('admins', [])
self._description = self._content.get('description', '')
- self._accessible = AccessibleTime(self._content['accessible'])
- self._registration = AccessibleTime(self._content['registration'])
+ self._accessible = AccessibleTime(self._content.get('accessible'))
+ self._registration = AccessibleTime(self._content.get('registration'))
self._registration_password = self._content.get('registration_password')
self._registration_ac = self._content.get('registration_ac')
if self._registration_ac not in [None, "username", "binding", "email"]:
From d56d8fd6fc39ef8898d274805b4701133f7b1a97 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Mon, 29 Apr 2024 11:25:03 +0200
Subject: [PATCH 39/49] [frontend/settings] Changing course accessibility
structure when updating settings instead of legacy migration
---
inginious/frontend/course_factory.py | 6 ------
inginious/frontend/pages/course_admin/settings.py | 9 +++++++++
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/inginious/frontend/course_factory.py b/inginious/frontend/course_factory.py
index 4bdd202f8..c56ef32c1 100644
--- a/inginious/frontend/course_factory.py
+++ b/inginious/frontend/course_factory.py
@@ -11,7 +11,6 @@
from inginious.frontend.exceptions import CourseNotFoundException, CourseAlreadyExistsException, TasksetNotFoundException
from inginious.frontend.courses import Course
-from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes
class CourseFactory(object):
@@ -114,12 +113,7 @@ def _migrate_legacy_courses(self):
cleaned_taskset_descriptor["dispenser_data"] = taskset_descriptor.get("dispenser_data", {})
taskset_descriptor["tasksetid"] = courseid
taskset_descriptor["admins"] = taskset_descriptor.get("admins", []) + taskset_descriptor.get("tutors", [])
- if "accessible" in taskset_descriptor:
- taskset_descriptor["accessible"] = change_access_structure(taskset_descriptor["accessible"])
- if "registration" in taskset_descriptor:
- taskset_descriptor["registration"] = change_access_structure(taskset_descriptor["registration"])
- taskset_descriptor = dict_data_str_to_datetimes(taskset_descriptor)
self._database.courses.update_one({"_id": courseid}, {"$set": taskset_descriptor}, upsert=True)
self._taskset_factory.update_taskset_descriptor_content(courseid, cleaned_taskset_descriptor)
except TasksetNotFoundException as e:
diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py
index 8ef71ffad..d7b24c197 100644
--- a/inginious/frontend/pages/course_admin/settings.py
+++ b/inginious/frontend/pages/course_admin/settings.py
@@ -6,6 +6,7 @@
import re
import flask
from datetime import datetime
+import copy
from inginious.common.base import dict_from_prefix, id_checker
from inginious.frontend.user_settings.field_types import FieldTypes
@@ -40,6 +41,14 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['groups_student_choice'] = True if data["groups_student_choice"] == "true" else False
+ if isinstance(data["accessible"], (str, bool)):
+ course_content["accessible"] = {} # problème -> immutableDict -> on ne peut pas changer le type
+ if isinstance(data["registration"], (str, bool)):
+ course_content["registration"] = {}
+
+ # what if not present in data (-> None) or empty string (-> "") ?
+ # => problems with filling settings form and updating settings
+
if data["accessible"] == "custom":
course_content['accessible']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else course._accessible.min
course_content['accessible']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else course._accessible.max
From 4f7f40ac63b5e4cd33141fc38f33269e58445cc6 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Mon, 29 Apr 2024 16:35:25 +0200
Subject: [PATCH 40/49] [frontend/task_list] Moving task access structure
update to legacy update
---
inginious/frontend/pages/course_admin/task_list.py | 3 ++-
inginious/frontend/pages/taskset_admin/template.py | 3 ++-
inginious/frontend/task_dispensers/toc.py | 11 +++++++++--
3 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index 983d6294c..b33939f27 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -38,7 +38,8 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
task_dispenser = course.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- for taskid, task in data["config"].items():
+ dispenser_data = task_dispenser.get_dispenser_data()
+ for task in dispenser_data["config"].values():
task["accessibility"] = dict_data_str_to_datetimes(
change_access_structure(task["accessibility"], True))
self.update_dispenser(course, data)
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 6805e7a6f..267c7c841 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -39,7 +39,8 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ
task_dispenser = taskset.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- for taskid, task in data["config"].items():
+ dispenser_data = task_dispenser.get_dispenser_data()
+ for task in dispenser_data["config"].values():
task["accessibility"] = dict_data_str_to_datetimes(
change_access_structure(task["accessibility"], True))
self.update_dispenser(taskset, data)
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index e72859b83..197983b9a 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -13,7 +13,7 @@
SubmissionLimit, Accessibility
from inginious.frontend.task_dispensers import TaskDispenser
from inginious.frontend.accessible_time import AccessibleTime
-from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes, dict_data_datetimes_to_str
+from inginious.frontend.util import dict_data_datetimes_to_str
class TableOfContents(TaskDispenser):
@@ -144,7 +144,14 @@ def get_ordered_tasks(self):
return OrderedDict([(taskid, tasks[taskid]) for taskid in self._toc.get_tasks() if taskid in tasks])
def has_legacy_tasks(self, ignore_imported=False):
- """ Checks if the task files contains dispenser settings """
+ """ Checks if the task files contains dispenser settings or if the tasks have legacy structure """
+
+ if "config" in self._dispenser_data:
+ # if old accessibility structure
+ for task in self._dispenser_data["config"].values():
+ if isinstance(task["accessibility"], str):
+ return True
+
if not ignore_imported and self._dispenser_data.get("imported", False):
return False
From b615b928bf161f36b743546e92ace228af3e8bb5 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Mon, 6 May 2024 16:42:44 +0200
Subject: [PATCH 41/49] Revert "[frontend/task_list] Moving task access
structure update to legacy update"
This reverts commit d03a4e91b4bbc332b5305ad461da94c6205b617e.
---
inginious/frontend/pages/course_admin/task_list.py | 3 +--
inginious/frontend/pages/taskset_admin/template.py | 3 +--
inginious/frontend/task_dispensers/toc.py | 11 ++---------
3 files changed, 4 insertions(+), 13 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index b33939f27..983d6294c 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -38,8 +38,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
task_dispenser = course.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- dispenser_data = task_dispenser.get_dispenser_data()
- for task in dispenser_data["config"].values():
+ for taskid, task in data["config"].items():
task["accessibility"] = dict_data_str_to_datetimes(
change_access_structure(task["accessibility"], True))
self.update_dispenser(course, data)
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 267c7c841..6805e7a6f 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -39,8 +39,7 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ
task_dispenser = taskset.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- dispenser_data = task_dispenser.get_dispenser_data()
- for task in dispenser_data["config"].values():
+ for taskid, task in data["config"].items():
task["accessibility"] = dict_data_str_to_datetimes(
change_access_structure(task["accessibility"], True))
self.update_dispenser(taskset, data)
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index 197983b9a..e72859b83 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -13,7 +13,7 @@
SubmissionLimit, Accessibility
from inginious.frontend.task_dispensers import TaskDispenser
from inginious.frontend.accessible_time import AccessibleTime
-from inginious.frontend.util import dict_data_datetimes_to_str
+from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes, dict_data_datetimes_to_str
class TableOfContents(TaskDispenser):
@@ -144,14 +144,7 @@ def get_ordered_tasks(self):
return OrderedDict([(taskid, tasks[taskid]) for taskid in self._toc.get_tasks() if taskid in tasks])
def has_legacy_tasks(self, ignore_imported=False):
- """ Checks if the task files contains dispenser settings or if the tasks have legacy structure """
-
- if "config" in self._dispenser_data:
- # if old accessibility structure
- for task in self._dispenser_data["config"].values():
- if isinstance(task["accessibility"], str):
- return True
-
+ """ Checks if the task files contains dispenser settings """
if not ignore_imported and self._dispenser_data.get("imported", False):
return False
From 26e065890783b5299674d3bc731ff85536a48b11 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Mon, 6 May 2024 16:56:32 +0200
Subject: [PATCH 42/49] [frontend/task_dispenser] Updating task accessibility
structure when tasks settings update
Previously done when importing legacy tasks
---
inginious/frontend/accessible_time.py | 12 ++++++
.../frontend/pages/course_admin/task_list.py | 5 +--
.../frontend/pages/taskset_admin/template.py | 5 +--
.../task_dispensers/combinatory_test.py | 4 +-
inginious/frontend/task_dispensers/toc.py | 12 ++++--
inginious/frontend/util.py | 42 -------------------
6 files changed, 26 insertions(+), 54 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 4d01521c2..6f93ff974 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -149,3 +149,15 @@ def get_end_date(self):
def get_soft_end_date(self):
""" Return a datetime object, representing the soft deadline for accessibility """
return self._soft_end
+
+ def string_date(self, date):
+ """ Returns the date as a string """
+ return date.strftime("%4Y-%m-%d %H:%M:%S")
+
+ def get_string_dict(self):
+ """ Returns a dictionary with the start, end and soft_end as strings """
+ return {
+ "start": self.string_date(self._start),
+ "soft_end": self.string_date(self._soft_end),
+ "end": self.string_date(self._end)
+ }
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index 983d6294c..863f3636c 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -10,7 +10,7 @@
from natsort import natsorted
from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage
-from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes
+from inginious.frontend.util import dict_data_str_to_datetimes
class CourseTaskListPage(INGIniousAdminPage):
""" List informations about all tasks """
@@ -39,8 +39,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
try:
data = task_dispenser.import_legacy_tasks()
for taskid, task in data["config"].items():
- task["accessibility"] = dict_data_str_to_datetimes(
- change_access_structure(task["accessibility"], True))
+ task["accessibility"] = dict_data_str_to_datetimes(task["accessibility"])
self.update_dispenser(course, data)
except Exception as e:
errors.append(_("Something wrong happened: ") + str(e))
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 6805e7a6f..69d66c9a2 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -10,7 +10,7 @@
from natsort import natsorted
from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage
-from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes
+from inginious.frontend.util import dict_data_str_to_datetimes
class TasksetTemplatePage(INGIniousAdminPage):
@@ -40,8 +40,7 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ
try:
data = task_dispenser.import_legacy_tasks()
for taskid, task in data["config"].items():
- task["accessibility"] = dict_data_str_to_datetimes(
- change_access_structure(task["accessibility"], True))
+ task["accessibility"] = dict_data_str_to_datetimes(task["accessibility"])
self.update_dispenser(taskset, data)
except Exception as e:
errors.append(_("Something wrong happened: ") + str(e))
diff --git a/inginious/frontend/task_dispensers/combinatory_test.py b/inginious/frontend/task_dispensers/combinatory_test.py
index 4c587fbcb..319f3674c 100644
--- a/inginious/frontend/task_dispensers/combinatory_test.py
+++ b/inginious/frontend/task_dispensers/combinatory_test.py
@@ -12,7 +12,6 @@
from inginious.frontend.task_dispensers.util import SectionConfigItem, Weight, SubmissionStorage, EvaluationMode, \
Categories, SubmissionLimit, Accessibility
from inginious.frontend.accessible_time import AccessibleTime
-from inginious.frontend.util import dict_data_datetimes_to_str
class CombinatoryTest(TableOfContents):
@@ -55,10 +54,9 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
- task_config = dict_data_datetimes_to_str(self._task_config)
return template_helper.render("task_dispensers_admin/combinatory_test.html", element=element, course=course, taskset=taskset,
- dispenser_structure=self._toc, dispenser_config=task_config, tasks=task_data, task_errors=task_errors, config_fields=config_fields)
+ dispenser_structure=self._toc, dispenser_config=self._task_config, tasks=task_data, task_errors=task_errors, config_fields=config_fields)
def render(self, template_helper, course, tasks_data, tag_list, username):
""" Returns the formatted task list"""
diff --git a/inginious/frontend/task_dispensers/toc.py b/inginious/frontend/task_dispensers/toc.py
index e72859b83..303e15852 100644
--- a/inginious/frontend/task_dispensers/toc.py
+++ b/inginious/frontend/task_dispensers/toc.py
@@ -13,7 +13,6 @@
SubmissionLimit, Accessibility
from inginious.frontend.task_dispensers import TaskDispenser
from inginious.frontend.accessible_time import AccessibleTime
-from inginious.frontend.util import change_access_structure, dict_data_str_to_datetimes, dict_data_datetimes_to_str
class TableOfContents(TaskDispenser):
@@ -38,6 +37,7 @@ def __init__(self, task_list_func, dispenser_data, database, element_id):
self._toc = SectionsList(dispenser_data.get("toc", {}))
self._task_config = dispenser_data.get("config", {})
parse_tasks_config(self._task_list_func().keys(), self.config_items, self._task_config)
+ self._task_config = self.adapt_accessibilities(self._task_config)
@classmethod
def get_id(cls):
@@ -116,10 +116,9 @@ def render_edit(self, template_helper, element, task_data, task_errors):
taskset = element if isinstance(element, inginious.frontend.tasksets.Taskset) else None
course = element if isinstance(element, inginious.frontend.courses.Course) else None
- task_config = dict_data_datetimes_to_str(self._task_config)
return template_helper.render("task_dispensers_admin/toc.html", element=element, course=course, taskset=taskset,
- dispenser_structure=self._toc, dispenser_config=task_config, tasks=task_data,
+ dispenser_structure=self._toc, dispenser_config=self._task_config, tasks=task_data,
task_errors=task_errors, config_fields=config_fields)
def render(self, template_helper, course, tasks_data, tag_list, username):
@@ -163,3 +162,10 @@ def import_legacy_tasks(self):
raise Exception(f"In task {taskid} : {e}")
dispenser_data["imported"] = True
return dispenser_data
+
+ def adapt_accessibilities(self, task_config): # better name ? -> function to pass data through all potential objects
+ """ Adapts the task accessibilities to the new format by passing them through the AccessibleTime object"""
+ for task in task_config:
+ accessibility = AccessibleTime(Accessibility.get_value(self._task_config.get(task, {}))) # reproduces .get_accessibilities from TableOfContent
+ task_config[task]["accessibility"] = accessibility.get_string_dict()
+ return task_config
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
index 9cf9df8ff..12fbd6119 100644
--- a/inginious/frontend/util.py
+++ b/inginious/frontend/util.py
@@ -39,45 +39,3 @@ def dict_data_str_to_datetimes(data):
for index, item in enumerate(data):
dict_data_str_to_datetimes(item)
return data
-
-
-def change_access_structure(access_data, needs_soft_end=False):
- """
- Transforms old access structure (course access and registration, task access) into new structure.
- ex: "accessible" can be a boolean or a concatenation of start and end dates ("start/end").
- It will be transformed to have this structure:
- "accessible": {"start": ..., "end": ...}
- "registration": {"start": ..., "end": ...}
- "accessibility": {"start": ...,"soft_end": ..., "end": ...}
- When one of the dates is not given in a custom access or always/never accessible, it will be set to a max or min date.
- examples:
- "registration": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
- "accessible": {"start": "2023-11-24 16:44:56", "end": "2023-11-24 16:44:56"}
- :param access_data: dict, old access structure
- :param needs_soft_end: bool, True if the new structure needs a soft_end date in the structure
- :return: dict, new access structure
- """
-
- new_access_data = {"start": None, "end": None}
-
- if isinstance(access_data, bool):
- new_access_data["end"] = "9999-12-31 23:59:59"
- if needs_soft_end:
- new_access_data["soft_end"] = "9999-12-31 23:59:59"
- if access_data:
- new_access_data["start"] = "0001-01-01 00:00:00"
- else:
- new_access_data["start"] = "9999-12-31 23:59:59"
-
-
- elif isinstance(access_data, str) and access_data != "":
- dates = access_data.split("/")
- if needs_soft_end:
- new_access_data["start"] = dates[0]
- new_access_data["soft_end"] = dates[1]
- new_access_data["end"] = dates[2]
- else:
- new_access_data["start"] = dates[0]
- new_access_data["end"] = dates[1]
-
- return new_access_data
\ No newline at end of file
From 5776d6ee16cd3250ef64b1a1dad6109bd6d56bf4 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 7 May 2024 09:59:07 +0200
Subject: [PATCH 43/49] Fixing Codacy warnings
---
inginious/frontend/accessible_time.py | 21 +++++-------
.../frontend/pages/course_admin/settings.py | 32 +++++++++----------
.../frontend/pages/course_admin/task_list.py | 2 +-
inginious/frontend/pages/marketplace.py | 2 +-
.../frontend/pages/taskset_admin/template.py | 2 +-
inginious/frontend/pages/tasksets.py | 2 +-
.../task_dispensers/combinatory_test.py | 1 -
inginious/frontend/task_dispensers/util.py | 1 -
inginious/frontend/util.py | 12 +++++--
9 files changed, 37 insertions(+), 38 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 6f93ff974..ed6d82417 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -73,15 +73,13 @@ def __init__(self, period):
for key, date in period.items():
if not isinstance(date, (datetime, str)):
raise Exception("Wrong period given to AccessibleTime")
- elif isinstance(date, str):
+ if isinstance(date, str):
period[key] = parse_date(date)
self._start = period["start"]
self._end = period["end"]
if "soft_end" in period:
- self._soft_end = period["soft_end"]
- if self._soft_end > self._end:
- self._soft_end = self._end
+ self._soft_end = min(period["soft_end"], period["end"])
def before_start(self, when=None):
@@ -100,14 +98,14 @@ def is_open(self, when=None):
if when is None:
when = datetime.now()
- return self._start <= when and when <= self._end
+ return self._start <= when <= self._end
def is_open_with_soft_deadline(self, when=None):
""" Returns True if the course/task is still open with the soft deadline """
if when is None:
when = datetime.now()
- return self._start <= when and when <= self._soft_end
+ return self._start <= when <= self._soft_end
def is_always_accessible(self):
""" Returns true if the course/task is always accessible """
@@ -119,24 +117,21 @@ def is_never_accessible(self):
def get_std_start_date(self):
""" If the date is custom, return the start datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
- if self._start != self.min and self._start != self.max:
+ if self._start not in [self.min, self.max]:
return self._start.strftime("%4Y-%m-%d %H:%M:%S")
- else:
- return ""
+ return ""
def get_std_end_date(self):
""" If the date is custom, return the end datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._end != self.max:
return self._end.strftime("%4Y-%m-%d %H:%M:%S")
- else:
- return ""
+ return ""
def get_std_soft_end_date(self):
""" If the date is custom, return the soft datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._soft_end != self.max:
return self._soft_end.strftime("%4Y-%m-%d %H:%M:%S")
- else:
- return ""
+ return ""
def get_start_date(self):
""" Return a datetime object, representing the date when the task/course become accessible """
diff --git a/inginious/frontend/pages/course_admin/settings.py b/inginious/frontend/pages/course_admin/settings.py
index d7b24c197..c12e573e5 100644
--- a/inginious/frontend/pages/course_admin/settings.py
+++ b/inginious/frontend/pages/course_admin/settings.py
@@ -3,10 +3,9 @@
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.
+from datetime import datetime
import re
import flask
-from datetime import datetime
-import copy
from inginious.common.base import dict_from_prefix, id_checker
from inginious.frontend.user_settings.field_types import FieldTypes
@@ -42,22 +41,21 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['groups_student_choice'] = True if data["groups_student_choice"] == "true" else False
if isinstance(data["accessible"], (str, bool)):
- course_content["accessible"] = {} # problème -> immutableDict -> on ne peut pas changer le type
+ course_content["accessible"] = {}
if isinstance(data["registration"], (str, bool)):
course_content["registration"] = {}
- # what if not present in data (-> None) or empty string (-> "") ?
- # => problems with filling settings form and updating settings
+ course_accessibility = course.get_accessibility()
if data["accessible"] == "custom":
- course_content['accessible']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else course._accessible.min
- course_content['accessible']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else course._accessible.max
+ course_content['accessible']["start"] = datetime.strptime(data["accessible_start"], '%Y-%m-%d %H:%M:%S') if data["accessible_start"] != "" else course_accessibility.min
+ course_content['accessible']["end"] = datetime.strptime(data["accessible_end"], '%Y-%m-%d %H:%M:%S') if data["accessible_end"] != "" else course_accessibility.max
elif data["accessible"] == "true":
- course_content['accessible']["start"] = course._accessible.min
- course_content['accessible']["end"] = course._accessible.max
+ course_content['accessible']["start"] = course_accessibility.min
+ course_content['accessible']["end"] = course_accessibility.max
else:
- course_content['accessible']["start"] = course._accessible.max
- course_content['accessible']["end"] = course._accessible.max
+ course_content['accessible']["start"] = course_accessibility.max
+ course_content['accessible']["end"] = course_accessibility.max
try:
AccessibleTime(course_content['accessible'])
@@ -68,14 +66,14 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
course_content['allow_preview'] = True if data["allow_preview"] == "true" else False
if data["registration"] == "custom":
- course_content['registration']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else course._accessible.min
- course_content['registration']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else course._accessible.max
+ course_content['registration']["start"] = datetime.strptime(data["registration_start"],'%Y-%m-%d %H:%M:%S') if data["registration_start"] != "" else course_accessibility.min
+ course_content['registration']["end"] = datetime.strptime(data["registration_end"], '%Y-%m-%d %H:%M:%S') if data["registration_end"] != "" else course_accessibility.max
elif data["registration"] == "true":
- course_content['registration']["start"] = course._accessible.min
- course_content['registration']["end"] = course._accessible.max
+ course_content['registration']["start"] = course_accessibility.min
+ course_content['registration']["end"] = course_accessibility.max
else:
- course_content['registration']["start"] = course._accessible.max
- course_content['registration']["end"] = course._accessible.max
+ course_content['registration']["start"] = course_accessibility.max
+ course_content['registration']["end"] = course_accessibility.max
try:
AccessibleTime(course_content['registration'])
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index 863f3636c..4a7957d25 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -38,7 +38,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
task_dispenser = course.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- for taskid, task in data["config"].items():
+ for task in data["config"].values():
task["accessibility"] = dict_data_str_to_datetimes(task["accessibility"])
self.update_dispenser(course, data)
except Exception as e:
diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py
index d9ba6b3ac..5c0809f48 100644
--- a/inginious/frontend/pages/marketplace.py
+++ b/inginious/frontend/pages/marketplace.py
@@ -5,10 +5,10 @@
""" Course page """
import sys
+from datetime import datetime
import flask
from flask import redirect
from werkzeug.exceptions import Forbidden
-from datetime import datetime
from inginious.common.base import id_checker
from inginious.frontend.exceptions import ImportTasksetException
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 69d66c9a2..0739acb7b 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -39,7 +39,7 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ
task_dispenser = taskset.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- for taskid, task in data["config"].items():
+ for task in data["config"].values():
task["accessibility"] = dict_data_str_to_datetimes(task["accessibility"])
self.update_dispenser(taskset, data)
except Exception as e:
diff --git a/inginious/frontend/pages/tasksets.py b/inginious/frontend/pages/tasksets.py
index a4d6b40d7..e1e699657 100644
--- a/inginious/frontend/pages/tasksets.py
+++ b/inginious/frontend/pages/tasksets.py
@@ -4,9 +4,9 @@
# more information about the licensing of this file.
""" Index page """
+from datetime import datetime
import flask
from collections import OrderedDict
-from datetime import datetime
from inginious.frontend.pages.utils import INGIniousAuthPage
from inginious.frontend.exceptions import CourseAlreadyExistsException
diff --git a/inginious/frontend/task_dispensers/combinatory_test.py b/inginious/frontend/task_dispensers/combinatory_test.py
index 319f3674c..82ebd9b5d 100644
--- a/inginious/frontend/task_dispensers/combinatory_test.py
+++ b/inginious/frontend/task_dispensers/combinatory_test.py
@@ -4,7 +4,6 @@
# more information about the licensing of this file.
from random import Random
-import copy
from datetime import datetime
import inginious
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index 6297139b9..a7e773306 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -382,4 +382,3 @@ def check_task_config(task_list, config_items, data):
return True, ''
except Exception as ex:
return False, str(ex)
-
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
index 12fbd6119..797bfe789 100644
--- a/inginious/frontend/util.py
+++ b/inginious/frontend/util.py
@@ -8,6 +8,10 @@
def dict_data_datetimes_to_str(data):
+ """
+ :param data: dict or list data to convert
+ :return: dict or list with datetime objects converted to strings
+ """
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, datetime):
@@ -15,12 +19,16 @@ def dict_data_datetimes_to_str(data):
else:
dict_data_datetimes_to_str(value)
elif isinstance(data, list):
- for index, item in enumerate(data):
+ for item in data:
dict_data_datetimes_to_str(item)
return data
def dict_data_str_to_datetimes(data):
+ """
+ :param data: dict or list data to convert
+ :return: dict or list with string dates converted to datetime objects
+ """
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, str):
@@ -36,6 +44,6 @@ def dict_data_str_to_datetimes(data):
else:
dict_data_str_to_datetimes(value)
elif isinstance(data, list):
- for index, item in enumerate(data):
+ for item in data:
dict_data_str_to_datetimes(item)
return data
From ca8e5950bb56eb9376538ae34d6522f1c2b73f79 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 7 May 2024 14:19:12 +0200
Subject: [PATCH 44/49] [frontend/accessible_time] Fix legacy accessibility
string structure handling
---
inginious/frontend/accessible_time.py | 37 ++++++++++++++++------
inginious/frontend/task_dispensers/util.py | 3 ++
2 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index ed6d82417..654f2fbdb 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -52,17 +52,9 @@ def __init__(self, period):
if not isinstance(period, (dict, str, bool, type(None))): # add None check
raise Exception("Wrong period given to AccessibleTime")
- # if legacy format ( start/soft_end/end string, empty string, bool)
+ # if legacy format (start/soft_end/end string, empty string, bool)
if isinstance(period, str):
- period = list(filter(lambda c: c, period.split("/"))) # splits and removes empty strings
- if len(period) == 3:
- period = {"start": period[0], "soft_end": period[1], "end": period[2]}
- elif len(period) == 2:
- period = {"start": period[0], "soft_end": period[1], "end": period[1]}
- elif len(period) == 1:
- period = {"start": period[0], "soft_end": self.max, "end": self.max}
- else:
- period = {"start": self.min, "soft_end": self.max, "end": self.max}
+ period = self.legacy_string_structure_to_dict(period)
if isinstance(period, (bool, type(None))):
if period is (True or None):
period = {"start": self.min, "soft_end": self.max, "end": self.max}
@@ -81,6 +73,31 @@ def __init__(self, period):
if "soft_end" in period:
self._soft_end = min(period["soft_end"], period["end"])
+ def legacy_string_structure_to_dict(self, legacy_date):
+ """
+ Convert the legacy string structure to a dictionary. The legacy structure follows "start/soft_end/end" with
+ some of the values being optional. (ex: "start//end", "start//", "//end", ...).
+ :param legacy_date: string, legacy date structure
+ :return period: dict, containing the start, soft_end and end as strings
+ """
+ period = {}
+
+ values = legacy_date.split("/")
+ if len(values) == 1:
+ period["start"] = parse_date(values[0].strip(), datetime.min)
+ period["soft_end"] = datetime.max
+ period["end"] = datetime.max
+ elif len(values) == 2:
+ # Has start time and hard deadline
+ period["start"] = parse_date(values[0].strip(), datetime.min)
+ period["end"] = parse_date(values[1].strip(), datetime.max)
+ period["soft_end"] = period["end"]
+ else:
+ # Has start time, soft deadline and hard deadline
+ period["start"] = parse_date(values[0].strip(), datetime.min)
+ period["soft_end"] = parse_date(values[1].strip(), datetime.max)
+ period["end"] = parse_date(values[2].strip(), datetime.max)
+ return period
def before_start(self, when=None):
""" Returns True if the task/course is not yet accessible """
diff --git a/inginious/frontend/task_dispensers/util.py b/inginious/frontend/task_dispensers/util.py
index a7e773306..b85e1c38d 100644
--- a/inginious/frontend/task_dispensers/util.py
+++ b/inginious/frontend/task_dispensers/util.py
@@ -1,3 +1,6 @@
+"""
+Contains de task config item classes and the sections classes.
+"""
# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
From dafa7206b286cb15fcd5faa8c446d7a889c82588 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Tue, 7 May 2024 14:39:43 +0200
Subject: [PATCH 45/49] Fix codacy warning
---
inginious/frontend/util.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
index 797bfe789..cd9c08a5f 100644
--- a/inginious/frontend/util.py
+++ b/inginious/frontend/util.py
@@ -1,3 +1,6 @@
+"""
+Contains two functions that converts datetime objects in data structures into strings and vice versa.
+"""
# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
From 1a88ba27c434cca6248e74ea88772eabba01a465 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 16 May 2024 10:18:30 +0200
Subject: [PATCH 46/49] [frontend/accessible_time] Using self.min and max in
legacy_string_structure_to_dict()
---
inginious/frontend/accessible_time.py | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index 654f2fbdb..d582b4237 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -75,8 +75,9 @@ def __init__(self, period):
def legacy_string_structure_to_dict(self, legacy_date):
"""
- Convert the legacy string structure to a dictionary. The legacy structure follows "start/soft_end/end" with
- some of the values being optional. (ex: "start//end", "start//", "//end", ...).
+ Convert the legacy string structure to a dictionary. The legacy structure follows "start/soft_end/end" for
+ tasks or "start/end" for courses with some of the values being optional. Sometimes only a start date is
+ given as a string (ex: "start//end", "start//", "//end", "start/end", "start", "/end", ...).
:param legacy_date: string, legacy date structure
:return period: dict, containing the start, soft_end and end as strings
"""
@@ -84,19 +85,19 @@ def legacy_string_structure_to_dict(self, legacy_date):
values = legacy_date.split("/")
if len(values) == 1:
- period["start"] = parse_date(values[0].strip(), datetime.min)
- period["soft_end"] = datetime.max
- period["end"] = datetime.max
+ period["start"] = parse_date(values[0].strip(), self.min)
+ period["soft_end"] = self.max
+ period["end"] = self.max
elif len(values) == 2:
# Has start time and hard deadline
- period["start"] = parse_date(values[0].strip(), datetime.min)
- period["end"] = parse_date(values[1].strip(), datetime.max)
+ period["start"] = parse_date(values[0].strip(), self.min)
+ period["end"] = parse_date(values[1].strip(), self.max)
period["soft_end"] = period["end"]
else:
# Has start time, soft deadline and hard deadline
- period["start"] = parse_date(values[0].strip(), datetime.min)
- period["soft_end"] = parse_date(values[1].strip(), datetime.max)
- period["end"] = parse_date(values[2].strip(), datetime.max)
+ period["start"] = parse_date(values[0].strip(), self.min)
+ period["soft_end"] = parse_date(values[1].strip(), self.max)
+ period["end"] = parse_date(values[2].strip(), self.max)
return period
def before_start(self, when=None):
From 42811b0728fb9a72b56c784e8e5c6205533a7ea4 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 16 May 2024 15:23:53 +0200
Subject: [PATCH 47/49] [frontend] Fixing forgotten code that needed to be
removed
for removing accessibility structure from task import
---
.../frontend/pages/course_admin/task_list.py | 3 ---
.../frontend/pages/taskset_admin/template.py | 3 ---
inginious/frontend/util.py | 24 -------------------
3 files changed, 30 deletions(-)
diff --git a/inginious/frontend/pages/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py
index 4a7957d25..e06f462ca 100644
--- a/inginious/frontend/pages/course_admin/task_list.py
+++ b/inginious/frontend/pages/course_admin/task_list.py
@@ -10,7 +10,6 @@
from natsort import natsorted
from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage
-from inginious.frontend.util import dict_data_str_to_datetimes
class CourseTaskListPage(INGIniousAdminPage):
""" List informations about all tasks """
@@ -38,8 +37,6 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
task_dispenser = course.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- for task in data["config"].values():
- task["accessibility"] = dict_data_str_to_datetimes(task["accessibility"])
self.update_dispenser(course, data)
except Exception as e:
errors.append(_("Something wrong happened: ") + str(e))
diff --git a/inginious/frontend/pages/taskset_admin/template.py b/inginious/frontend/pages/taskset_admin/template.py
index 0739acb7b..cc27bca01 100644
--- a/inginious/frontend/pages/taskset_admin/template.py
+++ b/inginious/frontend/pages/taskset_admin/template.py
@@ -10,7 +10,6 @@
from natsort import natsorted
from inginious.frontend.pages.taskset_admin.utils import INGIniousAdminPage
-from inginious.frontend.util import dict_data_str_to_datetimes
class TasksetTemplatePage(INGIniousAdminPage):
@@ -39,8 +38,6 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ
task_dispenser = taskset.get_task_dispenser()
try:
data = task_dispenser.import_legacy_tasks()
- for task in data["config"].values():
- task["accessibility"] = dict_data_str_to_datetimes(task["accessibility"])
self.update_dispenser(taskset, data)
except Exception as e:
errors.append(_("Something wrong happened: ") + str(e))
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
index cd9c08a5f..1e494de86 100644
--- a/inginious/frontend/util.py
+++ b/inginious/frontend/util.py
@@ -26,27 +26,3 @@ def dict_data_datetimes_to_str(data):
dict_data_datetimes_to_str(item)
return data
-
-def dict_data_str_to_datetimes(data):
- """
- :param data: dict or list data to convert
- :return: dict or list with string dates converted to datetime objects
- """
- if isinstance(data, dict):
- for key, value in data.items():
- if isinstance(value, str):
- try:
- if value == "0001-01-01 00:00:00":
- data[key] = datetime.min
- elif value == "9999-12-31 23:59:59":
- data[key] = datetime.max.replace(microsecond=0)
- else:
- data[key] = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') if (value != "") else None
- except ValueError:
- pass # If it's not a valid date string, continue without converting
- else:
- dict_data_str_to_datetimes(value)
- elif isinstance(data, list):
- for item in data:
- dict_data_str_to_datetimes(item)
- return data
From fed730d18b4ab2b6f5b24f756dc1ea90f6ba0577 Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Thu, 16 May 2024 15:45:59 +0200
Subject: [PATCH 48/49] [frontend/util] Removing util.py file with unused
method
---
inginious/frontend/util.py | 28 ----------------------------
1 file changed, 28 deletions(-)
delete mode 100644 inginious/frontend/util.py
diff --git a/inginious/frontend/util.py b/inginious/frontend/util.py
deleted file mode 100644
index 1e494de86..000000000
--- a/inginious/frontend/util.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""
-Contains two functions that converts datetime objects in data structures into strings and vice versa.
-"""
-# -*- coding: utf-8 -*-
-#
-# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
-# more information about the licensing of this file.
-
-from datetime import datetime
-
-
-
-def dict_data_datetimes_to_str(data):
- """
- :param data: dict or list data to convert
- :return: dict or list with datetime objects converted to strings
- """
- if isinstance(data, dict):
- for key, value in data.items():
- if isinstance(value, datetime):
- data[key] = value.strftime("%4Y-%m-%d %H:%M:%S")
- else:
- dict_data_datetimes_to_str(value)
- elif isinstance(data, list):
- for item in data:
- dict_data_datetimes_to_str(item)
- return data
-
From 05daaf2e9134f66af7fb2c006f41f0184c29ec2e Mon Sep 17 00:00:00 2001
From: AlexandreDoneux <94830560+AlexandreDoneux@users.noreply.github.com>
Date: Fri, 17 May 2024 15:21:31 +0200
Subject: [PATCH 49/49] [frontend/accessible_time] Fixing soft_end set to end
when soft_end not given
---
inginious/frontend/accessible_time.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/inginious/frontend/accessible_time.py b/inginious/frontend/accessible_time.py
index d582b4237..714674801 100644
--- a/inginious/frontend/accessible_time.py
+++ b/inginious/frontend/accessible_time.py
@@ -71,7 +71,11 @@ def __init__(self, period):
self._start = period["start"]
self._end = period["end"]
if "soft_end" in period:
- self._soft_end = min(period["soft_end"], period["end"])
+ if period["soft_end"] == self.max:
+ self._soft_end = self.max
+ else:
+ soft_end = min(period["soft_end"], period["end"])
+ self._soft_end = soft_end
def legacy_string_structure_to_dict(self, legacy_date):
"""