Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add matrix checker page, exercises x users per course #351

Merged
merged 8 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# Python's Course LMS

<p align="center">
<img title="BSD-3 Clause" src="https://img.shields.io/github/license/PythonFreeCourse/LMS.svg">
<img title="Travis (.com) branch" src="https://img.shields.io/travis/com/PythonFreeCourse/LMS/master.svg">
<img title="BSD-3 Clause" src="https://img.shields.io/github/license/PythonFreeCourse/LMS.svg" alt="LMS License is BSD-3 Clause">
</p>

👋 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:

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions devops/i18n.sh
Original file line number Diff line number Diff line change
@@ -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
87 changes: 84 additions & 3 deletions lms/lmsdb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -167,8 +168,85 @@
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),
)
)
Comment on lines +171 to +184
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Consider optimizing the query for get_students.

This method could potentially return a large number of records. Consider adding pagination or limiting the number of records returned.


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

Check warning on line 249 in lms/lmsdb/models.py

View check run for this annotation

Codecov / codecov/patch

lms/lmsdb/models.py#L249

Added line #L249 was not covered by tests


class User(UserMixin, BaseModel):
Expand All @@ -181,6 +259,9 @@
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)

Expand Down
2 changes: 1 addition & 1 deletion lms/lmstests/public/unittests/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?'),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code_refinement): Consider the tone of user-facing messages.

While the change in message tone to be more informal might be intended to be friendly, ensure it aligns with the overall tone and user experience of the application.

)
notifications.send(
kind=notifications.NotificationKind.UNITTEST_ERROR,
Expand Down
1 change: 1 addition & 0 deletions lms/lmsweb/routes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
SOLUTIONS = '/view'
STATUS = '/status'
SUBMISSIONS = '/submissions'
DOWNLOADS = '/download'
SHARED = '/shared'
GIT = '/git/<int:course_id>/<int:exercise_number>.git'
Loading
Loading