diff --git a/README.md b/README.md index b033cbca..fcedbc20 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,29 @@ # Python's Course LMS

- - + LMS License is BSD-3 Clause

馃憢 Welcome to Python course learning management system. 馃悕 The system objectives - -1. Allow teachers and mentors to input exercises list and provide feedback/comments to students exercises solutions. +1. Allow teachers and mentors to input exercises list and provide feedback/comments + to students exercises solutions. 2. Allow students to load their exercises solutions and get feedback to their work. ## Creating development environment ### Prerequisites -1. Linux based system - either [WSL on windows](https://docs.microsoft.com/en-us/windows/wsl/install-win10) or full blown linux. -2. [Python](https://www.python.org/downloads/release/python-385/) -3. [Docker](https://docs.docker.com/docker-for-windows/install/) and docker-compose. - +1. Linux based system - either [WSL on windows](https://docs.microsoft.com/en-us/windows/wsl/install-win10) + or full blown Linux. +2. Latest Python version +3. Docker ### Minimal setup -This setup is for debug purposes and will use SQLite database and frontend only. +This setup is for debug purposes and will use SQLite database and front-end only. Steps to do: @@ -48,8 +48,8 @@ cd .. flask run # Run in root directory ``` -After logging in, use [localhost admin](https://127.0.0.1:5000/admin) to modify entries in the database. - +After logging in, use [localhost admin](https://127.0.0.1:5000/admin) +to modify entries in the database. ### Full setup @@ -82,25 +82,34 @@ docker exec -it lms_http_1 bash python lmsdb/bootstrap.py ``` -Enter http://127.0.0.1:8080, and the initial credentials should appear in your terminal. :) +Enter [http://127.0.0.1:8080], and the initial credentials should appear in +your terminal. :) -After logging in, use [localhost admin](https://127.0.0.1:8080/admin) to modify entries in the database. +After logging in, use [localhost admin](https://127.0.0.1:8080/admin) to +modify entries in the database. In case you want to enable the mail system: 1. Insert your mail details in the configuration file. 2. Change the `DISABLE_MAIL` line value to False. - ## Code modification check list -## Run flake8 +## Run Flake8 ```bash -# on lms root directory +# on LMS root directory flake8 lms ``` +### Updating localization files + +```bash +. devops/i18n.sh update +``` + +Then go to lms/lmsweb/translations to translate the strings, if needed. + ### Run tests ```bash diff --git a/devops/i18n.sh b/devops/i18n.sh index c16ff7d1..6fbe88df 100644 --- a/devops/i18n.sh +++ b/devops/i18n.sh @@ -1,9 +1,18 @@ #!/bin/bash -x -SCRIPT_FILE_PATH=$(readlink -f "${0}") -SCRIPT_FOLDER=$(dirname "${SCRIPT_FILE_PATH}") -MAIN_FOLDER="${SCRIPT_FOLDER}/.." +original_dir="$(pwd)" +cd "$(dirname "${0:-${BASH_SOURCE[0]}}")" || return +cd .. + + +if [ "${1}" = "update" ]; then + echo "Updating translations" + pybabel extract -F "lms/babel.cfg" -o "lms/lmsweb/translations/messages.pot" "lms" + pybabel update -i "lms/lmsweb/translations/messages.pot" -d "lms/lmsweb/translations" +fi echo "Compiling Flask Babel" -pybabel compile -d "${MAIN_FOLDER}/lms/lmsweb/translations" +pybabel compile -d "lms/lmsweb/translations" + +cd "$original_dir" || return diff --git a/lms/lmsdb/models.py b/lms/lmsdb/models.py index 4b5d5d99..1b6ecca5 100644 --- a/lms/lmsdb/models.py +++ b/lms/lmsdb/models.py @@ -13,8 +13,9 @@ from flask_babel import gettext as _ # type: ignore from flask_login import UserMixin, current_user # type: ignore from peewee import ( # type: ignore - BooleanField, Case, CharField, Check, DateTimeField, ForeignKeyField, - IntegerField, JOIN, ManyToManyField, TextField, UUIDField, fn, + BooleanField, Case, CharField, Check, Database, DateTimeField, + ForeignKeyField, IntegerField, JOIN, ManyToManyField, Select, TextField, + UUIDField, fn, ) from playhouse.signals import ( # type: ignore Model, post_delete, post_save, pre_save, @@ -167,8 +168,85 @@ def public_courses(cls): def public_course_exists(cls): return cls.public_courses().exists() + def get_students(self, fields=None) -> List[int]: + fields = fields or [User.id] + + return ( + self + .select(*fields) + .join(UserCourse) + .join(User, on=(User.id == UserCourse.user_id)) + .join(Role) + .where( + (self.id == UserCourse.course_id) + & (Role.id == Role.get_student_role().id), + ) + ) + + def get_exercise_ids(self) -> List[int]: + return [e.id for e in self.exercise] + + def get_matrix(self, database: Database = database) -> dict: + SolutionAlias = Solution.alias() + fields = [ + SolutionAlias.id.alias('solution_id'), + SolutionAlias.solver.id.alias('solver_id'), + SolutionAlias.exercise.id.alias('exercise_id'), + ] + + query = ( + User + .select(*fields) + .join(Exercise, JOIN.CROSS) + .join(Course, JOIN.LEFT_OUTER, on=(Exercise.course == Course.id)) + .join(SolutionAlias, JOIN.LEFT_OUTER, on=( + (SolutionAlias.exercise == Exercise.id) + & (SolutionAlias.solver == User.id) + )) + .where( + (Exercise.id << self.exercise), + (User.id << self.get_students()), + ) + .group_by(Exercise.id, User.id, SolutionAlias.id) + .having( + (SolutionAlias.id == fn.MAX(SolutionAlias.id)) + | (SolutionAlias.id.is_null(True)), + ) + .alias('solutions_subquery') + ) + + full_query_fields = [ + query.c.solver_id, + query.c.exercise_id, + Solution.id.alias('solution_id'), + User.fullname.alias('checker'), + Solution.state, + Solution.submission_timestamp, + SolutionAssessment.name.alias('assessment'), + SolutionAssessment.icon.alias('assessment_icon'), + ] + + solutions = ( + Select(columns=full_query_fields) + .from_(query) + .join(Solution, JOIN.LEFT_OUTER, on=( + Solution.id == query.c.solution_id + )) + .join(SolutionAssessment, JOIN.LEFT_OUTER, on=( + (Solution.assessment == SolutionAssessment.id) + )) + .join(User, JOIN.LEFT_OUTER, on=(Solution.checker == User.id)) + ) + + query_results = solutions.execute(database) + + return { + (row['exercise_id'], row['solver_id']): row + for row in query_results + } + def __str__(self): - return f'{self.name}: {self.date} - {self.end_date}' + return self.name class User(UserMixin, BaseModel): @@ -181,6 +259,9 @@ class User(UserMixin, BaseModel): last_course_viewed = ForeignKeyField(Course, null=True) uuid = UUIDField(default=uuid4, unique=True) + class Meta: + table_name = "user" + def get_id(self): return str(self.uuid) diff --git a/lms/lmstests/public/unittests/services.py b/lms/lmstests/public/unittests/services.py index 4dad0724..728f6c8a 100644 --- a/lms/lmstests/public/unittests/services.py +++ b/lms/lmstests/public/unittests/services.py @@ -141,7 +141,7 @@ def _handle_failed_to_execute_tests(self, raw_results: bytes) -> None: solution=self._solution, test_name=models.ExerciseTestName.FATAL_TEST_NAME, user_message=fail_user_message, - staff_message=_('Bro, did you check your code?'), + staff_message=_('Woah! Did you check your code?'), ) notifications.send( kind=notifications.NotificationKind.UNITTEST_ERROR, diff --git a/lms/lmsweb/routes.py b/lms/lmsweb/routes.py index 21b17e62..3c12b44e 100644 --- a/lms/lmsweb/routes.py +++ b/lms/lmsweb/routes.py @@ -1,5 +1,6 @@ SOLUTIONS = '/view' STATUS = '/status' +SUBMISSIONS = '/submissions' DOWNLOADS = '/download' SHARED = '/shared' GIT = '/git//.git' diff --git a/lms/lmsweb/translations/he/LC_MESSAGES/messages.po b/lms/lmsweb/translations/he/LC_MESSAGES/messages.po index 20cd5355..0e85644b 100644 --- a/lms/lmsweb/translations/he/LC_MESSAGES/messages.po +++ b/lms/lmsweb/translations/he/LC_MESSAGES/messages.po @@ -5,487 +5,512 @@ # msgid "" msgstr "" -"Project-Id-Version: 1.0\n" +"Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-10-15 14:37+0300\n" -"PO-Revision-Date: 2021-10-17 01:59+0300\n" -"Last-Translator: Or Ronai\n" +"POT-Creation-Date: 2024-03-23 18:51+0200\n" +"PO-Revision-Date: 2024-03-23 18:59+0300\n" +"Last-Translator: Yam Mesicka\n" "Language: he\n" "Language-Team: he \n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.9.1\n" -"X-Generator: Poedit 3.0\n" -"X-Poedit-Bookmarks: -1,-1,-1,-1,0,-1,-1,-1,-1,-1\n" +"Generated-By: Babel 2.14.0\n" -#: lmsdb/models.py:921 +#: lms/lmsdb/models.py:998 msgid "Fatal error" msgstr "讻讬砖诇讜谉 讞诪讜专" -#: lmstests/public/identical_tests/services.py:103 +#: lms/lmstests/public/identical_tests/services.py:103 #, python-format msgid "Your solution for the %(subject)s exercise has been checked." msgstr "讛驻转专讜谉 砖诇讱 诇转专讙讬诇 %(subject)s 谞讘讚拽." -#: lmstests/public/linters/services.py:95 +#: lms/lmstests/public/linters/services.py:95 #, python-format msgid "The automatic checker gave you %(errors_num)d for your %(name)s solution." msgstr "讛讘讜讚拽 讛讗讜讟讜诪讟讬 谞转谉 %(errors_num)d 讛注专讜转 注诇 转专讙讬诇讱 %(name)s." -#: lmstests/public/unittests/services.py:16 lmstests/sandbox/linters/base.py:16 +#: lms/lmstests/public/unittests/services.py:16 +#: lms/lmstests/sandbox/linters/base.py:16 msgid "The automatic checker couldn't run your code." msgstr "讛讘讜讚拽 讛讗讜讟讜诪讟讬 诇讗 讛爪诇讬讞 诇讛专讬抓 讗转 讛拽讜讚 砖诇讱." -#: lmstests/public/unittests/services.py:120 +#: lms/lmstests/public/unittests/services.py:122 #, python-format msgid "" -"The automatic checker failed in %(number)d examples in your \"%(subject)s\" " -"solution." +"The automatic checker failed in %(number)d examples in your " +"\"%(subject)s\" solution." msgstr "讛讘讜讚拽 讛讗讜讟讜诪讟讬 谞讻砖诇 讘志 %(number)d 讚讜讙诪讗讜转 讘转专讙讬诇 \"%(subject)s\"." -#: lmstests/public/unittests/services.py:142 -msgid "Bro, did you check your code?" -msgstr "讗讞讬, 讘讚拽转 讗转 讛拽讜讚 砖诇讱?" +#: lms/lmstests/public/unittests/services.py:144 +#, fuzzy +msgid "Woah! Did you check your code?" +msgstr "讜讜讛讜! 讘讚拽转 讗转 讛拽讜讚 砖诇讱?" -#: lmsweb/views.py:130 +#: lms/lmsweb/views.py:183 msgid "Can not register now" msgstr "诇讗 谞讬转谉 诇讛讬专砖诐 讻注转" -#: lmsweb/views.py:147 +#: lms/lmsweb/views.py:205 msgid "Registration successfully" msgstr "讛讛专砖诪讛 讘讜爪注讛 讘讛爪诇讞讛" -#: lmsweb/views.py:170 +#: lms/lmsweb/views.py:233 msgid "The confirmation link is expired, new link has been sent to your email" msgstr "拽讬砖讜专 讛讗讬诪讜转 驻讙 转讜拽祝, 拽讬砖讜专 讞讚砖 谞砖诇讞 讗诇 转讬讘转 讛诪讬讬诇 砖诇讱" -#: lmsweb/views.py:186 +#: lms/lmsweb/views.py:252 msgid "Your user has been successfully confirmed, you can now login" msgstr "讛诪砖转诪砖 砖诇讱 讗讜诪转 讘讛爪诇讞讛, 讻注转 讗转讛 讬讻讜诇 诇讛转讞讘专 诇诪注专讻转" -#: lmsweb/views.py:209 lmsweb/views.py:266 +#: lms/lmsweb/views.py:277 lms/lmsweb/views.py:345 msgid "Your password has successfully changed" msgstr "讛住讬住诪讛 砖诇讱 砖讜谞转讛 讘讛爪诇讞讛" -#: lmsweb/views.py:225 +#: lms/lmsweb/views.py:295 msgid "Password reset link has successfully sent" msgstr "拽讬砖讜专 诇讗讬驻讜住 讛住讬住诪讛 谞砖诇讞 讘讛爪诇讞讛" -#: lmsweb/views.py:246 +#: lms/lmsweb/views.py:321 msgid "Reset password link is expired" msgstr "讛拽讬砖讜专 诇讗讬驻讜住 讛住讬住诪讛 驻讙 转讜拽祝" -#: lmsweb/forms/change_password.py:20 lmsweb/forms/register.py:32 -#: lmsweb/forms/reset_password.py:25 +#: lms/lmsweb/forms/change_password.py:20 lms/lmsweb/forms/register.py:32 +#: lms/lmsweb/forms/reset_password.py:25 msgid "The passwords are not identical" msgstr "讛住讬住诪讗讜转 砖讛讜讝谞讜 讗讬谞谉 讝讛讜转" -#: lmsweb/forms/change_password.py:31 +#: lms/lmsweb/forms/change_password.py:31 msgid "Invalid old password has been inserted too many times" msgstr "讛讝谞转 住讬住诪讛 砖讙讜讬讛 诪住驻专 专讘 诪讚讬 砖诇 驻注诪讬诐" -#: lmsweb/forms/change_password.py:35 +#: lms/lmsweb/forms/change_password.py:35 msgid "Invalid current password" msgstr "讛住讬住诪讛 讛谞讜讻讞讬转 砖讛讜讝谞讛 砖讙讜讬讛" -#: lmsweb/forms/register.py:14 lmsweb/forms/reset_password.py:12 -#: lmsweb/tools/validators.py:29 +#: lms/lmsweb/forms/register.py:14 lms/lmsweb/forms/reset_password.py:12 +#: lms/lmsweb/tools/validators.py:29 msgid "Invalid email" msgstr "讻转讜讘转 讛讚讜讗专 讛讗诇拽讟专讜谞讬 砖讛讜讝谞讛 讗讬谞讛 转拽讬谞讛" -#: lmsweb/tools/validators.py:13 +#: lms/lmsweb/tools/validators.py:17 msgid "The username is already in use" msgstr "砖诐 讛诪砖转诪砖 讛讝讛 讻讘专 谞诪爪讗 讘砖讬诪讜砖" -#: lmsweb/tools/validators.py:21 +#: lms/lmsweb/tools/validators.py:23 msgid "The email is already in use" msgstr "讻转讜讘转 讛讚讜讗专 讛讗诇拽讟专讜谞讬 讛讝讜 讻讘专 谞诪爪讗转 讘砖讬诪讜砖" -#: models/solutions.py:52 +#: lms/models/solutions.py:52 #, python-format msgid "%(solver)s has replied for your \"%(subject)s\" check." msgstr "%(solver)s 讛讙讬讘 诇讱 注诇 讘讚讬拽转 转专讙讬诇 \"%(subject)s\"." -#: models/solutions.py:59 +#: lms/models/solutions.py:59 #, python-format msgid "%(checker)s replied for \"%(subject)s\"." msgstr "%(checker)s 讛讙讬讘 诇讱 注诇 转专讙讬诇 \"%(subject)s\"." -#: models/solutions.py:78 +#: lms/models/solutions.py:78 #, python-format msgid "Your solution for the \"%(subject)s\" exercise has been checked." msgstr "讛驻转专讜谉 砖诇讱 诇转专讙讬诇 \"%(subject)s\" 谞讘讚拽." -#: models/users.py:29 +#: lms/models/users.py:30 +msgid "User not found" +msgstr "诇讗 诪爪讗谞讜 讗转 讛诪砖转诪砖" + +#: lms/models/users.py:33 +msgid "User is not valid" +msgstr "诇讗 讝讬讛讬谞讜 讗转 讛诪砖转诪砖" + +#: lms/models/users.py:48 msgid "Invalid username or password" msgstr "砖诐 讛诪砖转诪砖 讗讜 讛住讬住诪讛 砖讛讜讝谞讜 诇讗 转拽讬谞讬诐" -#: models/users.py:32 +#: lms/models/users.py:51 msgid "You have to confirm your registration with the link sent to your email" msgstr "注诇讬讱 诇讗砖专 讗转 诪讬讬诇 讛讗讬诪讜转" -#: models/users.py:50 +#: lms/models/users.py:69 #, python-format msgid "You are already registered to %(course_name)s course." msgstr "讗转讛 讻讘专 专砖讜诐 诇拽讜专住 %(course_name)s." -#: templates/banned.html:8 templates/login.html:7 -#: templates/recover-password.html:8 templates/reset-password.html:8 -#: templates/signup.html:8 +#: lms/templates/banned.html:8 lms/templates/login.html:7 +#: lms/templates/recover-password.html:8 lms/templates/reset-password.html:8 +#: lms/templates/signup.html:8 msgid "Profile picture of the Python Course" msgstr "转诪讜谞转 讛驻专讜驻讬诇 砖诇 拽讜专住 驻讬讬转讜谉" -#: templates/banned.html:12 +#: lms/templates/banned.html:12 msgid "Your account has been suspended by the manager." msgstr "讛诪砖转诪砖 砖诇讱 讛讜砖注讛 注诇 讬讚讬 诪谞讛诇 讛诪注专讻转." -#: templates/banned.html:13 +#: lms/templates/banned.html:13 msgid "For more details please contact the management team." msgstr "诇驻专讟讬诐 谞讜住驻讬诐 讗谞讗 驻谞讛 讗诇 爪讜讜转 讛谞讬讛讜诇." -#: templates/base.html:6 +#: lms/templates/base.html:6 msgid "Exercise submission system for the Python Course" msgstr "诪注专讻转 讛讙砖转 转专讙讬诇讬诐 诇拽讜专住 驻讬讬转讜谉" -#: templates/change-password.html:8 templates/change-password.html:17 -#: templates/user.html:19 +#: lms/templates/change-password.html:8 lms/templates/change-password.html:17 +#: lms/templates/user.html:19 msgid "Change Password" msgstr "砖谞讛 住讬住诪讛" -#: templates/change-password.html:10 +#: lms/templates/change-password.html:10 msgid "Insert current password and new password for changing it:" msgstr "讛讝讬谞讜 住讬住诪讛 讬砖谞讛 讜住讬住诪讛 讞讚砖讛 诇爪讜专讱 砖讬谞讜讬 讛住讬住诪讛:" -#: templates/change-password.html:13 +#: lms/templates/change-password.html:13 msgid "Current password" msgstr "住讬住诪讛 谞讜讻讞讬转" -#: templates/change-password.html:14 templates/login.html:28 -#: templates/login.html:30 templates/recover-password.html:14 -#: templates/signup.html:18 +#: lms/templates/change-password.html:14 lms/templates/login.html:28 +#: lms/templates/login.html:30 lms/templates/recover-password.html:14 +#: lms/templates/signup.html:18 msgid "Password" msgstr "住讬住诪讛" -#: templates/change-password.html:15 templates/recover-password.html:15 -#: templates/signup.html:19 +#: lms/templates/change-password.html:15 lms/templates/recover-password.html:15 +#: lms/templates/signup.html:19 msgid "Password Confirmation" msgstr "讗讬诪讜转 住讬住诪讛" -#: templates/exercises.html:8 +#: lms/templates/exercises.html:8 msgid "Exercises" msgstr "转专讙讬诇讬诐" -#: templates/exercises.html:21 templates/view.html:126 +#: lms/templates/exercises.html:21 msgid "Comments for the solution" msgstr "讛注专讜转 注诇 讛转专讙讬诇" -#: templates/exercises.html:30 +#: lms/templates/exercises.html:30 msgid "Send" msgstr "砖诇讞" -#: templates/exercises.html:32 +#: lms/templates/exercises.html:32 msgid "View" msgstr "讛爪爪讛" -#: templates/exercises.html:34 +#: lms/templates/exercises.html:34 msgid "Check" msgstr "诇讘讚讬拽讛" -#: templates/exercises.html:53 +#: lms/templates/exercises.html:53 msgid "All Exercises" msgstr "诇讻诇 讛转专讙讬诇讬诐" -#: templates/login.html:8 templates/login.html:35 +#: lms/templates/login.html:8 lms/templates/login.html:35 msgid "Login" msgstr "讛转讞讘专讜转" -#: templates/login.html:10 templates/signup.html:11 +#: lms/templates/login.html:10 lms/templates/signup.html:11 msgid "Welcome to the exercise system!" msgstr "讘专讜讻讬诐 讛讘讗讬诐 诇诪注专讻转 讛转专讙讬诇讬诐!" -#: templates/login.html:11 +#: lms/templates/login.html:11 msgid "Insert your username and password:" msgstr "讛讝讬谞讜 讗转 砖诐 讛诪砖转诪砖 讜讛住讬住诪讛 砖诇讻诐:" -#: templates/login.html:22 templates/login.html:24 templates/signup.html:16 -#: templates/user.html:11 +#: lms/templates/login.html:22 lms/templates/login.html:24 +#: lms/templates/signup.html:16 lms/templates/user.html:11 msgid "Username" msgstr "砖诐 诪砖转诪砖" -#: templates/login.html:37 +#: lms/templates/login.html:37 msgid "Forgot your password?" msgstr "砖讻讞转 讗转 讛住讬住诪讛?" -#: templates/login.html:40 templates/signup.html:22 +#: lms/templates/login.html:40 lms/templates/signup.html:22 msgid "Register" msgstr "讛讬专砖诐" -#: templates/navbar.html:21 +#: lms/templates/navbar.html:21 msgid "Messages" msgstr "讛讜讚注讜转" -#: templates/navbar.html:37 +#: lms/templates/navbar.html:37 msgid "Mark all as read" msgstr "住诪谉 讛讻诇 讻谞拽专讗" -#: templates/navbar.html:45 +#: lms/templates/navbar.html:45 msgid "Courses List" msgstr "专砖讬诪转 讛拽讜专住讬诐" -#: templates/navbar.html:65 +#: lms/templates/navbar.html:65 msgid "Upload Exercises" msgstr "讛注诇讗转 转专讙讬诇讬诐" -#: templates/navbar.html:73 +#: lms/templates/navbar.html:73 msgid "Exercises List" msgstr "专砖讬诪转 讛转专讙讬诇讬诐" -#: templates/navbar.html:81 +#: lms/templates/navbar.html:81 msgid "Exercises Archive" msgstr "讗专讻讬讜谉 讛转专讙讬诇讬诐" -#: templates/navbar.html:91 +#: lms/templates/navbar.html:91 msgid "Check Exercises" msgstr "讘讚讜拽 转专讙讬诇讬诐" -#: templates/navbar.html:98 +#: lms/templates/navbar.html:98 msgid "Logout" msgstr "讛转谞转拽讜转" -#: templates/public-courses.html:6 +#: lms/templates/public-courses.html:6 msgid "Public Courses List" msgstr "专砖讬诪转 拽讜专住讬诐 驻转讜讞讬诐" -#: templates/recover-password.html:9 templates/recover-password.html:17 -#: templates/reset-password.html:9 +#: lms/templates/recover-password.html:9 lms/templates/recover-password.html:17 +#: lms/templates/reset-password.html:9 msgid "Reset Password" msgstr "讗驻住 住讬住诪讛" -#: templates/recover-password.html:11 +#: lms/templates/recover-password.html:11 msgid "Insert password for changing it:" msgstr "讛讝讬谞讜 住讬住诪讛 诇爪讜专讱 砖讬谞讜讬 讛住讬住诪讛:" -#: templates/reset-password.html:11 +#: lms/templates/reset-password.html:11 msgid "Insert your email for getting link to reset it:" msgstr "讛讝讬谞讜 诪讬讬诇 诇爪讜专讱 砖诇讬讞转 拽讬砖讜专 诇讗讬驻讜住 讛住讬住诪讛:" -#: templates/reset-password.html:14 templates/signup.html:15 -#: templates/user.html:12 +#: lms/templates/reset-password.html:14 lms/templates/signup.html:15 +#: lms/templates/user.html:12 msgid "Email Address" msgstr "讻转讜讘转 讗讬诪讬讬诇" -#: templates/reset-password.html:15 +#: lms/templates/reset-password.html:15 msgid "Send Reset Password Link" msgstr "砖诇讞 诪讬讬诇 讗讬驻讜住 住讬住诪讛" -#: templates/reset-password.html:18 templates/signup.html:25 +#: lms/templates/reset-password.html:18 lms/templates/signup.html:25 msgid "Back to login page" msgstr "讞讝专讛 诇讚祝 讛讛转讞讘专讜转" -#: templates/signup.html:9 +#: lms/templates/signup.html:9 msgid "Registration" msgstr "讛专砖诪讛" -#: templates/signup.html:12 +#: lms/templates/signup.html:12 msgid "Insert your email and password for registration:" msgstr "讛讝讬谞讜 诪讬讬诇 讜住讬住诪讛 诇爪讜专讱 专讬砖讜诐 诇诪注专讻转:" -#: templates/signup.html:17 +#: lms/templates/signup.html:17 msgid "Full Name" msgstr "砖诐 诪诇讗" -#: templates/status.html:7 +#: lms/templates/status.html:7 msgid "Exercises operations room" msgstr "讞诪\"诇 转专讙讬诇讬诐" -#: templates/status.html:12 +#: lms/templates/status.html:12 msgid "Name" msgstr "砖诐" -#: templates/status.html:13 templates/user.html:47 +#: lms/templates/status.html:13 lms/templates/submissions-table.html:38 +#: lms/templates/user.html:47 msgid "Checked" msgstr "谞讘讚拽讜" -#: templates/status.html:14 +#: lms/templates/status.html:14 msgid "Solved" msgstr "谞驻转专讜" -#: templates/status.html:15 +#: lms/templates/status.html:15 msgid "Percentage" msgstr "讘讗讞讜讝讬诐" -#: templates/status.html:16 +#: lms/templates/status.html:16 msgid "Measurement" msgstr "诪讚讚" -#: templates/status.html:31 +#: lms/templates/status.html:31 msgid "Archive" msgstr "讗专讻讬讜谉" -#: templates/upload.html:7 +#: lms/templates/submissions-table.html:6 +#, fuzzy +msgid "Submissions table" +msgstr "诪爪讘 讛讙砖讛" + +#: lms/templates/submissions-table.html:10 lms/templates/user.html:33 +msgid "Exercise name" +msgstr "砖诐 转专讙讬诇" + +#: lms/templates/submissions-table.html:31 +msgid "By" +msgstr "注诇志讬讚讬" + +#: lms/templates/submissions-table.html:34 lms/templates/user.html:47 +msgid "Submitted" +msgstr "讛讜讙砖" + +#: lms/templates/submissions-table.html:36 +#, fuzzy +msgid "Checking..." +msgstr "讘讘讚讬拽讛..." + +#: lms/templates/submissions-table.html:40 +msgid "Error? This is an old solution" +msgstr "砖讙讬讗讛? 讝讛讜 驻转专讜谉 讬砖谉" + +#: lms/templates/upload.html:7 msgid "Upload Notebooks" msgstr "讛注诇讗转 诪讞讘专讜转" -#: templates/upload.html:11 +#: lms/templates/upload.html:11 msgid "Drag here the notebook file or click and choose it from your computer." msgstr "讙专专讜 诇讻讗谉 讗转 拽讜讘抓 讛诪讞讘专转, 讗讜 诇讞爪讜 讜讘讞专讜 讗讜转讛 诪讛诪讞砖讘 砖诇讻诐." -#: templates/upload.html:14 +#: lms/templates/upload.html:14 msgid "Back to Exercises List" msgstr "讞讝专讜 诇专砖讬诪转 讛转专讙讬诇讬诐" -#: templates/upload.html:17 +#: lms/templates/upload.html:17 msgid "Matches" msgstr "讛讜注诇讜" -#: templates/upload.html:18 +#: lms/templates/upload.html:18 msgid "Misses" msgstr "谞讻砖诇讜" -#: templates/user.html:9 +#: lms/templates/user.html:9 msgid "User details" msgstr "驻专讟讬 诪砖转诪砖" -#: templates/user.html:16 +#: lms/templates/user.html:16 msgid "Actions" msgstr "驻注讜诇讜转" -#: templates/user.html:21 +#: lms/templates/user.html:21 msgid "Join Courses" msgstr "讛讬专砖诐 诇拽讜专住讬诐" -#: templates/user.html:27 +#: lms/templates/user.html:27 msgid "Exercises Submitted" msgstr "转专讙讬诇讬诐 砖讛讜讙砖讜" -#: templates/user.html:32 +#: lms/templates/user.html:32 msgid "Course name" msgstr "砖诐 拽讜专住" -#: templates/user.html:33 -msgid "Exercise name" -msgstr "砖诐 转专讙讬诇" - -#: templates/user.html:34 +#: lms/templates/user.html:34 msgid "Submission status" msgstr "诪爪讘 讛讙砖讛" -#: templates/user.html:35 +#: lms/templates/user.html:35 msgid "Submission" msgstr "讛讙砖讛" -#: templates/user.html:36 +#: lms/templates/user.html:36 msgid "Checker" msgstr "讘讜讚拽" -#: templates/user.html:37 templates/view.html:25 templates/view.html:112 +#: lms/templates/user.html:37 lms/templates/view.html:25 +#: lms/templates/view.html:114 msgid "Assessment" msgstr "讛注专讻讛 诪讬诇讜诇讬转" -#: templates/user.html:47 -msgid "Submitted" -msgstr "讛讜讙砖" - -#: templates/user.html:47 +#: lms/templates/user.html:47 msgid "Not submitted" msgstr "诇讗 讛讜讙砖" -#: templates/user.html:59 +#: lms/templates/user.html:59 msgid "Notes" msgstr "驻转拽讬讜转" -#: templates/user.html:64 templates/user.html:66 +#: lms/templates/user.html:64 lms/templates/user.html:66 msgid "New Note" msgstr "驻转拽讬转 讞讚砖讛" -#: templates/user.html:70 +#: lms/templates/user.html:70 msgid "Related Exercise" msgstr "注讘讜专 转专讙讬诇" -#: templates/user.html:79 +#: lms/templates/user.html:79 msgid "Privacy Level" msgstr "专诪转 驻专讟讬讜转" -#: templates/user.html:85 +#: lms/templates/user.html:85 msgid "Add Note" msgstr "讛讜住祝 驻转拽讬转" -#: templates/view.html:6 +#: lms/templates/view.html:6 msgid "Exercise view" msgstr "转爪讜讙转 转专讙讬诇" -#: templates/view.html:9 +#: lms/templates/view.html:9 msgid "Your solution had checked!" msgstr "讛转专讙讬诇 砖诇讱 谞讘讚拽!" -#: templates/view.html:9 +#: lms/templates/view.html:9 msgid "Click on the red lines in order to see the comments." msgstr "诇讞爪讜 注诇 讛砖讜专讜转 讛讗讚讜诪讜转 讻讚讬 诇专讗讜转 讗转 讛注专讜转 讛讘讜讚拽讬诐." -#: templates/view.html:11 +#: lms/templates/view.html:11 msgid "Your solution is being checked in these moments!" msgstr "讛转专讙讬诇 砖诇讱 谞讘讚拽 讘专讙注讬诐 讗诇讜!" -#: templates/view.html:13 +#: lms/templates/view.html:13 msgid "This solution is not up to date!" msgstr "驻转专讜谉 讝讛 讗讬谞讜 注讚讻谞讬!" -#: templates/view.html:15 +#: lms/templates/view.html:15 msgid "Your solution hasn't been checked." msgstr "讛驻转专讜谉 砖诇讱 注讚讬讬谉 诇讗 谞讘讚拽." -#: templates/view.html:15 +#: lms/templates/view.html:15 msgid "It's important for us that all exercises will be checked by human eye." msgstr "讞砖讜讘 诇谞讜 砖讻诇 转专讙讬诇 讬注讘讜专 讘讚讬拽讛 砖诇 注讬谉 讗谞讜砖讬转." -#: templates/view.html:19 +#: lms/templates/view.html:19 msgid "Solver" msgstr "诪讙讬砖" -#: templates/view.html:32 +#: lms/templates/view.html:32 msgid "Navigate in solution versions" msgstr "谞讬讜讜讟 讘讙专住讗讜转 讛讛讙砖讛" -#: templates/view.html:38 +#: lms/templates/view.html:38 msgid "Current page" msgstr "住讬住诪讛 谞讜讻讞讬转" -#: templates/view.html:46 +#: lms/templates/view.html:46 msgid "Finish Checking" msgstr "住讬讜诐 讘讚讬拽讛" -#: templates/view.html:86 +#: lms/templates/view.html:86 msgid "Automatic Checking" msgstr "讘讚讬拽讜转 讗讜讟讜诪讟讬讜转" -#: templates/view.html:93 +#: lms/templates/view.html:93 msgid "Error" msgstr "讻讬砖诇讜谉" -#: templates/view.html:98 +#: lms/templates/view.html:98 msgid "Staff Error" msgstr "讻讬砖诇讜谉 住讙诇" -#: templates/view.html:134 -msgid "General comments" -msgstr "讛注专讜转 讻诇诇讬讜转" - -#: templates/view.html:142 -msgid "Checker comments" -msgstr "讛注专讜转 讘讜讚拽" +#: lms/templates/view.html:130 +#, fuzzy +msgid "Comments for this exercise" +msgstr "讛注专讜转 注诇 讛转专讙讬诇" -#: templates/view.html:152 +#: lms/templates/view.html:138 msgid "Done Checking" msgstr "住讬讬诐 诇讘讚讜拽" -#: utils/mail.py:25 +#: lms/utils/mail.py:25 #, python-format msgid "Confirmation mail - %(site_name)s" msgstr "讛讜讚注转 讗讬诪讜转 - %(site_name)s" -#: utils/mail.py:32 +#: lms/utils/mail.py:32 #, python-format msgid "" "Hello %(fullname)s,\n" @@ -494,12 +519,12 @@ msgstr "" "砖诇讜诐 %(fullname)s,\n" "拽讬砖讜专 讛讗讬诪讜转 砖诇讱 诇诪注专讻转 讛讜讗: %(link)s" -#: utils/mail.py:42 +#: lms/utils/mail.py:42 #, python-format msgid "Reset password mail - %(site_name)s" msgstr "讛讜讚注讛 注诇 讗讬驻讜住 住讬住诪讛 - %(site_name)s" -#: utils/mail.py:49 +#: lms/utils/mail.py:49 #, python-format msgid "" "Hello %(fullname)s,\n" @@ -508,18 +533,20 @@ msgstr "" "砖诇讜诐 %(fullname)s,\n" "讛拽讬砖讜专 诇爪讜专讱 讗讬驻讜住 讛住讬住诪讛 砖诇讱 讛讜讗: %(link)s" -#: utils/mail.py:58 +#: lms/utils/mail.py:58 #, python-format msgid "Changing password - %(site_name)s" msgstr "砖讬谞讜讬 住讬住诪讛 - %(site_name)s" -#: utils/mail.py:62 +#: lms/utils/mail.py:62 #, python-format msgid "" -"Hello %(fullname)s. Your password in %(site_name)s site has been changed.\n" +"Hello %(fullname)s. Your password in %(site_name)s site has been changed." +"\n" "If you didn't do it please contact with the site management.\n" "Mail address: %(site_mail)s" msgstr "" "砖诇讜诐 %(fullname)s. 讛住讬住诪讛 砖诇讱 讘讗转专 %(site_name)s 砖讜谞转讛.\n" "讗诐 诇讗 讗转讛 砖讬谞讬转 讗转 讛住讬住诪讛, 爪讜专 拽砖专 注诐 讛谞讛诇转 讛讗转专.\n" "讻转讜讘转 讛诪讬讬诇 砖诇讱 诇驻讬 专讬砖讜诪讬谞讜: %(site_mail)s" + diff --git a/lms/lmsweb/views.py b/lms/lmsweb/views.py index 265a834b..76ae9bc1 100644 --- a/lms/lmsweb/views.py +++ b/lms/lmsweb/views.py @@ -72,6 +72,8 @@ ) from lms.models import ( comments, + courses, + exercises, notes, notifications, share_link, @@ -404,7 +406,7 @@ def overview_status(): ) -@webapp.route(f'/course//{routes.STATUS.strip("/")}/') +@webapp.route(f'/course//{routes.STATUS.strip("/")}/detailed/') @managers_only @login_required def status(course_id: int): @@ -414,7 +416,28 @@ def status(course_id: int): ) -@webapp.route("/course/") +@webapp.route(f'/course//{routes.STATUS.strip("/")}/') +@managers_only +@login_required +def submissions_table(course_id: int): + course = Course.get_or_none(course_id) + if course is None: + return fail(404, f'No such course {course_id}.') + + course_exercises = exercises.get_basic_exercises_view(course_id) + course_users = courses.get_students(course_id) + solutions_matrix = course.get_matrix() + + return render_template( + 'submissions-table.html', + exercises=course_exercises, + users=course_users, + solutions=solutions_matrix, + course_id=course_id, + ) + + +@webapp.route('/course/') @login_required def change_last_course_viewed(course_id: int): course = Course.get_or_none(course_id) @@ -823,7 +846,7 @@ def done_checking(exercise_id: int, solution_id: int): return jsonify({"success": is_updated, "next": next_solution_id}) -@webapp.route("/check/") +@webapp.route("/check/exercise/") @login_required @managers_only def start_checking(exercise_id): @@ -833,6 +856,22 @@ def start_checking(exercise_id): return redirect(routes.STATUS) +@webapp.route("/check/solution/") +@login_required +@managers_only +def check_solution(solution_id: int): + solution = Solution.get_or_none(Solution.id == solution_id) + + if solution is None: + return fail(404, "Solution does not exist.") + if solution.is_checked: + return redirect(url_for("view", solution_id=solution.id)) + + if solutions.start_checking(solution): + return redirect(url_for("view", solution_id=solution.id)) + return redirect(routes.STATUS) + + @webapp.route("/common_comments") @webapp.route("/common_comments/") @login_required diff --git a/lms/models/courses.py b/lms/models/courses.py new file mode 100644 index 00000000..a8b28a9c --- /dev/null +++ b/lms/models/courses.py @@ -0,0 +1,9 @@ +from typing import NamedTuple + +from lms.lmsdb.models import Course, User + + +def get_students(course_id: int) -> NamedTuple: + fields = [User.id, User.username, User.fullname, Course.name] + course = Course.get_by_id(course_id) + return course.get_students(fields=fields).namedtuples() diff --git a/lms/models/exercises.py b/lms/models/exercises.py new file mode 100644 index 00000000..f116d9d9 --- /dev/null +++ b/lms/models/exercises.py @@ -0,0 +1,14 @@ +from typing import Optional + +from lms.lmsdb.models import Exercise + + +def get_basic_exercises_view(course_id: Optional[int]): + fields = [ + Exercise.id, Exercise.number, + Exercise.subject.alias('name'), Exercise.is_archived, + ] + query = Exercise.select(*fields) + if course_id is not None: + query = query.where(Exercise.course == course_id) + return query.namedtuples() diff --git a/lms/models/solutions.py b/lms/models/solutions.py index 6324a637..9d3d70f7 100644 --- a/lms/models/solutions.py +++ b/lms/models/solutions.py @@ -4,7 +4,7 @@ from zipfile import ZipFile from flask_babel import gettext as _ # type: ignore -from flask_login import current_user # type: ignore +from flask_login import current_user from playhouse.shortcuts import model_to_dict # type: ignore from lms.extractors.base import File @@ -140,6 +140,10 @@ def get_view_parameters( **view_params, 'exercise_common_comments': comments._common_comments(exercise_id=solution.exercise), + 'all_common_comments': + comments._common_comments(), + 'user_comments': + comments._common_comments(user_id=current_user.id), 'left': Solution.left_in_exercise(solution.exercise), 'assessments': SolutionAssessment.get_assessments(solution.exercise.course), diff --git a/lms/templates/navbar.html b/lms/templates/navbar.html index 603b2947..115afba0 100644 --- a/lms/templates/navbar.html +++ b/lms/templates/navbar.html @@ -86,7 +86,7 @@