diff --git a/inginious/common/custom_yaml.py b/inginious/common/custom_yaml.py index f86d0584b..ff3f64440 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,16 @@ def _long_str_representer(dumper, data): 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:%SZ") + return dumper.represent_scalar('tag:yaml.org,2002:timestamp', date) + + 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/accessible_time.py b/inginious/frontend/accessible_time.py index 4da1a370b..714674801 100644 --- a/inginious/frontend/accessible_time.py +++ b/inginious/frontend/accessible_time.py @@ -9,15 +9,25 @@ def parse_date(date, default=None): - """ Parse a valid date """ + """ + 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") - 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"]: + if date == "0001-01-01 00:00:00": + return datetime.min + if date == "9999-12-31 23:59:59": + 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"]: try: return datetime.strptime(date, format_type) except ValueError: @@ -28,55 +38,78 @@ 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, period): """ - 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 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" """ - 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] - 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.max = datetime.max.replace(microsecond=0) + self.min = datetime.min + 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 isinstance(period, str): + 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} + 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)): + raise Exception("Wrong period given to AccessibleTime") + if isinstance(date, str): + period[key] = parse_date(date) + + self._start = period["start"] + self._end = period["end"] + if "soft_end" in period: + 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): + """ + 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 + """ + period = {} + + values = legacy_date.split("/") + if len(values) == 1: + 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(), 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(), 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): """ Returns True if the task/course is not yet accessible """ 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,54 +120,61 @@ 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 <= 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 <= 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 == self.min and self._end == self.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 == 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 %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") - else: - return "" + """ If the date is custom, return the start datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """ + if self._start not in [self.min, self.max]: + return self._start.strftime("%4Y-%m-%d %H:%M:%S") + 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") - else: - return "" + """ 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") + 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") - else: - return "" + """ 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") + 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 """ 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/course_factory.py b/inginious/frontend/course_factory.py index 05e66d329..c56ef32c1 100644 --- a/inginious/frontend/course_factory.py +++ b/inginious/frontend/course_factory.py @@ -113,6 +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", []) + 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/courses.py b/inginious/frontend/courses.py index 74fc6e1cc..703b4dabb 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,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.get("accessible", None)) - self._registration = AccessibleTime(self._content.get("registration", None)) - self._registration_password = self._content.get('registration_password', None) - self._registration_ac = self._content.get('registration_ac', None) + 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"]: raise Exception("Course has an invalid value for registration_ac: " + self.get_id()) self._registration_ac_accept = self._content.get('registration_ac_accept', True) @@ -74,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) - self._registration = AccessibleTime(False) + 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/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/settings.py b/inginious/frontend/pages/course_admin/settings.py index fa230129f..c12e573e5 100644 --- a/inginious/frontend/pages/course_admin/settings.py +++ b/inginious/frontend/pages/course_admin/settings.py @@ -3,6 +3,7 @@ # 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 @@ -39,12 +40,22 @@ 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"] = {} + if isinstance(data["registration"], (str, bool)): + course_content["registration"] = {} + + course_accessibility = course.get_accessibility() + if data["accessible"] == "custom": - course_content['accessible'] = "{}/{}".format(data["accessible_start"], data["accessible_end"]) + 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'] = True + course_content['accessible']["start"] = course_accessibility.min + course_content['accessible']["end"] = course_accessibility.max else: - course_content['accessible'] = False + course_content['accessible']["start"] = course_accessibility.max + course_content['accessible']["end"] = course_accessibility.max try: AccessibleTime(course_content['accessible']) @@ -55,15 +66,18 @@ 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']["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'] = True + course_content['registration']["start"] = course_accessibility.min + course_content['registration']["end"] = course_accessibility.max else: - course_content['registration'] = False + course_content['registration']["start"] = course_accessibility.max + course_content['registration']["end"] = course_accessibility.max try: AccessibleTime(course_content['registration']) - except: + except Exception: errors.append(_('Invalid registration dates')) course_content['registration_password'] = data['registration_password'] 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/course_admin/task_list.py b/inginious/frontend/pages/course_admin/task_list.py index 71e85acaa..e06f462ca 100644 --- a/inginious/frontend/pages/course_admin/task_list.py +++ b/inginious/frontend/pages/course_admin/task_list.py @@ -11,7 +11,6 @@ from inginious.frontend.pages.course_admin.utils import INGIniousAdminPage - class CourseTaskListPage(INGIniousAdminPage): """ List informations about all tasks """ diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py index a92640a80..5c0809f48 100644 --- a/inginious/frontend/pages/marketplace.py +++ b/inginious/frontend/pages/marketplace.py @@ -5,6 +5,7 @@ """ Course page """ import sys +from datetime import datetime import flask from flask import redirect from werkzeug.exceptions import Forbidden @@ -82,7 +83,7 @@ def import_taskset(taskset, new_tasksetid, username, taskset_factory): try: new_descriptor = {"description": old_descriptor.get("description", ""), 'admins': [username], - "accessible": False, + "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/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/pages/tasksets.py b/inginious/frontend/pages/tasksets.py index 27a03a80a..e1e699657 100644 --- a/inginious/frontend/pages/tasksets.py +++ b/inginious/frontend/pages/tasksets.py @@ -4,6 +4,7 @@ # more information about the licensing of this file. """ Index page """ +from datetime import datetime import flask from collections import OrderedDict @@ -35,7 +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": False, "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/plugins/contests/__init__.py b/inginious/frontend/plugins/contests/__init__.py index 8fe079c1a..ef21770f9 100644 --- a/inginious/frontend/plugins/contests/__init__.py +++ b/inginious/frontend/plugins/contests/__init__.py @@ -35,8 +35,8 @@ def __init__(self, task_list_func, dispenser_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"), + "start": datetime.now(), + "end": datetime.now() + timedelta(hours=1), "blackout": 0, "penalty": 20} ) @@ -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'], "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) @@ -82,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) @@ -95,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) @@ -191,9 +192,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.course_factory.get_course_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.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 """ @@ -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: 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 @@