diff --git a/lms/lmsdb/bootstrap.py b/lms/lmsdb/bootstrap.py index f559759e..c4472f33 100644 --- a/lms/lmsdb/bootstrap.py +++ b/lms/lmsdb/bootstrap.py @@ -240,6 +240,12 @@ def _api_keys_migration() -> bool: return True +def _last_status_view_migration() -> bool: + Solution = models.Solution + _migrate_column_in_table_if_needed(Solution, Solution.last_status_view) + _migrate_column_in_table_if_needed(Solution, Solution.last_time_view) + + def _uuid_migration() -> bool: User = models.User _add_not_null_column(User, User.uuid, _add_uuid_to_users_table) @@ -248,6 +254,9 @@ def _uuid_migration() -> bool: def main(): with models.database.connection_context(): + if models.database.table_exists(models.Solution.__name__.lower()): + _last_status_view_migration() + if models.database.table_exists(models.User.__name__.lower()): _api_keys_migration() _uuid_migration() diff --git a/lms/lmsdb/models.py b/lms/lmsdb/models.py index eee18c6a..35c111d1 100644 --- a/lms/lmsdb/models.py +++ b/lms/lmsdb/models.py @@ -354,8 +354,20 @@ def to_choices(cls: enum.EnumMeta) -> Tuple[Tuple[str, str], ...]: return tuple((choice.name, choice.value) for choice in choices) +class SolutionStatusView(enum.Enum): + UPLOADED = 'Uploaded' + NOT_CHECKED = 'Not checked' + CHECKED = 'Checked' + + @classmethod + def to_choices(cls: enum.EnumMeta) -> Tuple[Tuple[str, str], ...]: + choices = cast(Iterable[enum.Enum], tuple(cls)) + return tuple((choice.name, choice.value) for choice in choices) + + class Solution(BaseModel): STATES = SolutionState + STATUS_VIEW = SolutionStatusView MAX_CHECK_TIME_SECONDS = 60 * 10 exercise = ForeignKeyField(Exercise, backref='solutions') @@ -371,6 +383,12 @@ class Solution(BaseModel): ) submission_timestamp = DateTimeField(index=True) hashed = TextField() + last_status_view = CharField( + choices=STATUS_VIEW.to_choices(), + default=STATUS_VIEW.UPLOADED.name, + index=True, + ) + last_time_view = DateTimeField(default=datetime.now, null=True, index=True) @property def solution_files( @@ -412,6 +430,20 @@ def is_duplicate( return last_submission_hash == hash_ + def view_solution(self) -> None: + self.last_time_view = datetime.now() + if ( + self.last_status_view != self.STATUS_VIEW.NOT_CHECKED.name + and self.state == self.STATES.CREATED.name + ): + self.last_status_view = self.STATUS_VIEW.NOT_CHECKED.name + elif ( + self.last_status_view != self.STATUS_VIEW.CHECKED.name + and self.state == self.STATES.DONE.name + ): + self.last_status_view = self.STATUS_VIEW.CHECKED.name + self.save() + def start_checking(self) -> bool: return self.set_state(Solution.STATES.IN_CHECKING) diff --git a/lms/lmsweb/views.py b/lms/lmsweb/views.py index 49ffdc3f..0c0022c3 100644 --- a/lms/lmsweb/views.py +++ b/lms/lmsweb/views.py @@ -576,6 +576,9 @@ def view( error_message, status_code = e.args return fail(status_code, error_message) + if viewer_is_solver: + solution.view_solution() + return render_template('view.html', **view_params) diff --git a/lms/static/my.css b/lms/static/my.css index 886ed538..f3acc0aa 100644 --- a/lms/static/my.css +++ b/lms/static/my.css @@ -81,6 +81,11 @@ a { color: #860606; } +#forgot-my-password-link { + display: block; + margin: 0.5rem; +} + .page { margin: 3rem 0; } diff --git a/lms/templates/login.html b/lms/templates/login.html index f76945de..20dd92a6 100644 --- a/lms/templates/login.html +++ b/lms/templates/login.html @@ -33,8 +33,8 @@