Skip to content

Commit 85ffa3a

Browse files
committed
- Added some tests
- Changed the exercise number pre_save - Removed unique from assessments oreder - Assessments are now per course
1 parent 4fb3dcd commit 85ffa3a

File tree

8 files changed

+113
-46
lines changed

8 files changed

+113
-46
lines changed

lms/lmsdb/models.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
from lms.models.errors import AlreadyExists
2626
from lms.utils import hashing
2727
from lms.utils.colors import get_hex_color
28-
from lms.utils.consts import DEFAULT_ACTIVE_COLOR, DEFAULT_COLOR
28+
from lms.utils.consts import (
29+
DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR, DEFAULT_ASSESSMENT_BUTTON_COLOR,
30+
)
2931
from lms.utils.log import log
3032

3133

@@ -370,12 +372,24 @@ def open_for_new_solutions(self) -> bool:
370372
return datetime.now() < self.due_date and not self.is_archived
371373

372374
@classmethod
373-
def get_highest_number(cls):
374-
return cls.select(fn.MAX(cls.number)).scalar()
375+
def get_highest_number(cls, course: Course):
376+
return (
377+
cls
378+
.select(cls.number)
379+
.where(cls.course == course)
380+
.order_by(cls.number.desc())
381+
.limit(1)
382+
.scalar()
383+
)
375384

376385
@classmethod
377-
def is_number_exists(cls, number: int) -> bool:
378-
return cls.select().where(cls.number == number).exists()
386+
def is_number_exists(cls, course: Course, number: int) -> bool:
387+
return (
388+
cls
389+
.select()
390+
.where(cls.course == course, cls.number == number)
391+
.exists()
392+
)
379393

380394
@classmethod
381395
def get_objects(
@@ -424,8 +438,8 @@ def __str__(self):
424438
def exercise_number_save_handler(model_class, instance, created):
425439
"""Change the exercise number to the highest consecutive number."""
426440

427-
if model_class.is_number_exists(instance.number):
428-
instance.number = model_class.get_highest_number() + 1
441+
if model_class.is_number_exists(instance.course, instance.number):
442+
instance.number = model_class.get_highest_number(instance.course) + 1
429443

430444

431445
class SolutionState(enum.Enum):
@@ -464,11 +478,12 @@ class SolutionAssessment(BaseModel):
464478
icon = CharField(null=True)
465479
color = CharField()
466480
active_color = CharField()
467-
order = IntegerField(default=0, index=True, unique=True)
481+
order = IntegerField(default=0, index=True)
482+
course = ForeignKeyField(Course, backref='assessments')
468483

469484
@classmethod
470-
def get_assessments(cls):
471-
return cls.select().order_by(cls.order)
485+
def get_assessments(cls, course: Course):
486+
return cls.select().where(cls.course == course).order_by(cls.order)
472487

473488
def __str__(self):
474489
return self.name
@@ -481,12 +496,12 @@ def assessment_on_save_handler(_model_class, instance, created):
481496
try:
482497
instance.color = get_hex_color(instance.color)
483498
except ValueError:
484-
instance.color = DEFAULT_COLOR
499+
instance.color = DEFAULT_ASSESSMENT_BUTTON_COLOR
485500

486501
try:
487502
instance.active_color = get_hex_color(instance.active_color)
488503
except ValueError:
489-
instance.active_color = DEFAULT_ACTIVE_COLOR
504+
instance.active_color = DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR
490505

491506

492507
class Solution(BaseModel):
@@ -1121,11 +1136,13 @@ def create_basic_assessments() -> None:
11211136
'color': 'black', 'icon': 'exclamation-triangle', 'order': 4,
11221137
},
11231138
}
1124-
for name, values in assessments_dict.items():
1125-
SolutionAssessment.create(
1126-
name=name, icon=values.get('icon'), color=values.get('color'),
1127-
active_color='white', order=values.get('order'),
1128-
)
1139+
courses = Course.select()
1140+
for course in courses:
1141+
for name, values in assessments_dict.items():
1142+
SolutionAssessment.create(
1143+
name=name, icon=values.get('icon'), color=values.get('color'),
1144+
active_color='white', order=values.get('order'), course=course,
1145+
)
11291146

11301147

11311148
def create_basic_course() -> Course:

lms/models/solutions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ def get_view_parameters(
142142
'user_comments':
143143
comments._common_comments(user_id=current_user.id),
144144
'left': Solution.left_in_exercise(solution.exercise),
145-
'assessments': SolutionAssessment.get_assessments(),
145+
'assessments':
146+
SolutionAssessment.get_assessments(solution.exercise.course),
146147
}
147148

148149
if viewer_is_solver:

lms/templates/user.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ <h2>{{ _('Exercises Submitted') }}:</h2>
3131
<th scope="col">{{ _('Submission status') }}</th>
3232
<th scope="col">{{ _('Submission') }}</th>
3333
<th scope="col">{{ _('Checker') }}</th>
34-
<th scope="col">{{ _('Verbal note') }}</th>
34+
<th scope="col">{{ _('Assessment') }}</th>
3535
</tr>
3636
</thead>
3737
<tbody>

lms/templates/view.html

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ <h1>{{ _('Exercise view') }} {{ solution['exercise']['id'] }}: {{ solution['exer
1515
<p class="unchecked-msg {{ direction }}"><strong>{{ _("Your solution hasn't been checked.") }}</strong> {{ _("It's important for us that all exercises will be checked by human eye.") }}</p>
1616
{% endif %}
1717
{% else %}
18-
<p id="solver"><strong>{{ _('Solver') }}:</strong> <a href="/user/{{ solution['solver']['id'] }}">{{ solution['solver']['fullname'] | e }}</a></p>
18+
<p id="solver">
19+
<strong>{{ _('Solver') }}: </strong>
20+
<a href="/user/{{ solution['solver']['id'] }}">{{ solution['solver']['fullname'] | e }}</a>
21+
</p>
1922
{% endif %}
2023
{% if solution.assessment and not shared_url %}
21-
<p id="assessment"><strong>{{ _('Verbal note') }}:</strong>{% if solution.assessment.icon %} <i class="fa fa-{{ solution.assessment.icon | e }}"></i>{% endif %} {{ solution.assessment.name | e }}</p>
24+
<p id="assessment">
25+
<strong>{{ _('Assessment') }}: </strong>
26+
{% if solution.assessment.icon %}
27+
<i class="fa fa-{{ solution.assessment.icon | e }}"> </i>
28+
{% endif %}{{ solution.assessment.name | e }}
29+
</p>
2230
{% endif %}
2331
{% if not shared_url %}
2432
<nav id="versions" aria-label="{{ _('Navigate in solution versions') }}">
@@ -101,11 +109,16 @@ <h5 class="test-name">
101109
{% if is_manager and not shared_url %}
102110
<div id="popular-comments">
103111
<div id="exercise-assessment" class="{{ direction }}">
104-
<h2>{{ _('Verbal note') }}</h2>
112+
<h2>{{ _('Assessment') }}</h2>
105113
<div class="btn-group-vertical" id="solution-assessment" role="group" aria-label="assessments radio toggle button group">
106114
{% for assessment in assessments %}
107-
<input type="radio" class="btn-check" name="assessment" id="btnradio{{ loop.index }}" autocomplete="off" value="{{ assessment.id }}" {%- if solution.assessment.name == assessment.name or loop.first %}checked{% endif -%}>
108-
<label class="btn btn-outline" style="--color: {{ assessment.color }}; --active-color: {{ assessment.active_color }};" for="btnradio{{ loop.index }}">{% if assessment.icon %}<i class="fa fa-{{ assessment.icon }}"></i> {% endif %}{{ assessment.name }}</label>
115+
<input type="radio" class="btn-check" name="assessment" id="btnradio{{ loop.index }}" autocomplete="off" value="{{ assessment.id }}"{% if solution.assessment.name == assessment.name or loop.first %} checked{% endif %}>
116+
<label class="btn btn-outline" style="--color: {{ assessment.color }}; --active-color: {{ assessment.active_color }};" for="btnradio{{ loop.index }}">
117+
{% if assessment.icon %}
118+
<i class="fa fa-{{ assessment.icon }}"> </i>
119+
{% endif %}
120+
{{ assessment.name }}
121+
</label>
109122
{% endfor %}
110123
</div>
111124
</div>

lms/utils/colors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
from lms.utils.consts import COLORS
55

66

7-
HEX_COLOR = re.compile(r'(?P<hex>#[a-f0-9]{6}|#[a-f0-9]{3})')
7+
HEX_COLOR = re.compile(r'#?(?P<hex>[a-f0-9]{6}|[a-f0-9]{3})')
88

99

1010
def get_hex_color(number: str) -> Optional[str]:
1111
if color := HEX_COLOR.match(number):
12-
return color.groupdict()['hex']
12+
return '#' + color.groupdict()['hex']
1313
elif color := COLORS.get(number):
1414
return color
1515
raise ValueError('This is not a valid hex color')

lms/utils/consts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
'teal': '#20c997', 'orange': '#fd7e14', 'pink': '#d63384',
1414
'purple': '#6f42c1', 'indigo': '#6610f2', 'light': '#f8f9fa',
1515
}
16-
DEFAULT_COLOR = '#0d6efd' # primary
17-
DEFAULT_ACTIVE_COLOR = '#fff' # white
16+
DEFAULT_ASSESSMENT_BUTTON_COLOR = '#0d6efd' # primary
17+
DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR = '#fff' # white

tests/conftest.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,6 @@ def populate_roles():
4646
Role.create(name=role.value)
4747

4848

49-
@pytest.fixture(autouse=True, scope='session')
50-
def populate_assessments():
51-
assessments_dict = {
52-
'Excellent': {'color': 'green', 'icon': 'star', 'order': 1},
53-
'Nice': {'color': 'blue', 'icon': 'check', 'order': 2},
54-
'Try again': {'color': 'red', 'icon': 'exclamation', 'order': 3},
55-
'Plagiarism': {
56-
'color': 'black', 'icon': 'exclamation-triangle', 'order': 4,
57-
},
58-
}
59-
for name, values in assessments_dict.items():
60-
SolutionAssessment.create(
61-
name=name, icon=values.get('icon'), color=values.get('color'),
62-
active_color='white', order=values.get('order'),
63-
)
64-
65-
6649
@pytest.fixture(autouse=True, scope='function')
6750
def db(db_in_memory):
6851
"""Rollback all operations between each test-case"""
@@ -352,6 +335,23 @@ def course() -> Course:
352335
return create_course()
353336

354337

338+
@pytest.fixture()
339+
def _assessments(course: Course) -> None:
340+
assessments_dict = {
341+
'Excellent': {'color': 'green', 'icon': 'star', 'order': 1},
342+
'Nice': {'color': 'blue', 'icon': 'check', 'order': 2},
343+
'Try again': {'color': 'red', 'icon': 'exclamation', 'order': 3},
344+
'Plagiarism': {
345+
'color': 'black', 'icon': 'exclamation-triangle', 'order': 4,
346+
},
347+
}
348+
for name, values in assessments_dict.items():
349+
SolutionAssessment.create(
350+
name=name, icon=values.get('icon'), color=values.get('color'),
351+
active_color='white', order=values.get('order'), course=course,
352+
)
353+
354+
355355
@pytest.fixture()
356356
def exercise(course: Course) -> Exercise:
357357
return create_exercise(course, 1)

tests/test_solutions.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from lms.lmsdb import models
99
from lms.lmsdb.models import (
1010
Comment, Course, Exercise, SharedSolution,
11-
Solution, SolutionStatusView, User,
11+
Solution, SolutionAssessment, SolutionStatusView, User,
1212
)
1313
from lms.lmstests.public.general import tasks as general_tasks
1414
from lms.lmsweb import routes
1515
from lms.models import notifications, solutions
1616
from lms.models.errors import ResourceNotFound
1717
from lms.models.solutions import get_view_parameters
18+
from lms.utils.consts import COLORS, DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR, DEFAULT_ASSESSMENT_BUTTON_COLOR
1819
from tests import conftest
1920

2021

@@ -591,3 +592,38 @@ def test_done_checking(
591592
content_type='application/json',
592593
)
593594
assert response.status_code == 200
595+
596+
@staticmethod
597+
def test_solution_assessment_color_on_create(course: Course, _assessments):
598+
assessment1 = SolutionAssessment.create(
599+
name='Ok', color='red', active_color='lmnop',
600+
order=5, course=course,
601+
)
602+
assert assessment1.color == COLORS.get('red')
603+
assert (
604+
assessment1.active_color == DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR,
605+
)
606+
607+
assessment2 = SolutionAssessment.create(
608+
name='Fine', color='invalid', active_color='abcdef',
609+
order=6, course=course,
610+
)
611+
assert assessment2.color == DEFAULT_ASSESSMENT_BUTTON_COLOR
612+
assert assessment2.active_color == '#abcdef'
613+
614+
@staticmethod
615+
def test_solution_assessment_on_save(course: Course, _assessments):
616+
assessment = SolutionAssessment.get(SolutionAssessment.order == 2)
617+
assessment.color = 'secondary'
618+
assessment.active_color = 'fff'
619+
assessment.save()
620+
assert assessment.color == COLORS.get('secondary')
621+
assert assessment.active_color == '#fff'
622+
623+
assessment.color = 'xox'
624+
assessment.active_color = 'invalid'
625+
assessment.save()
626+
assert assessment.color == DEFAULT_ASSESSMENT_BUTTON_COLOR
627+
assert (
628+
assessment.active_color == DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR,
629+
)

0 commit comments

Comments
 (0)