Skip to content

Commit 23d0479

Browse files
committed
- Changed tags color
- Added a bridge of tags in models folder - Added course and date_created column to the tags table - Added a constraint to the tags table
1 parent 865a4f4 commit 23d0479

File tree

8 files changed

+106
-44
lines changed

8 files changed

+106
-44
lines changed

lms/lmsdb/models.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -346,12 +346,21 @@ def on_notification_saved(
346346
instance.delete_instance()
347347

348348

349-
class ExerciseTagText(BaseModel):
350-
text = TextField(unique=True)
349+
class Tag(BaseModel):
350+
text = TextField()
351+
course = ForeignKeyField(Course)
352+
date_created = DateTimeField(default=datetime.now)
353+
354+
class Meta:
355+
indexes = (
356+
(('text', 'course_id'), True),
357+
)
351358

352359
@classmethod
353-
def create_tag(cls, text: str) -> 'ExerciseTagText':
354-
instance, _ = cls.get_or_create(**{cls.text.name: html.escape(text)})
360+
def create_tag(cls, text: str, course: Course) -> 'Tag':
361+
instance, _ = cls.get_or_create(
362+
**{cls.text.name: html.escape(text), cls.course.name: course},
363+
)
355364
return instance
356365

357366
def __str__(self):
@@ -388,12 +397,8 @@ def is_number_exists(cls, number: int) -> bool:
388397
return cls.select().where(cls.number == number).exists()
389398

390399
@classmethod
391-
def get_objects(
392-
cls, user_id: int, fetch_archived: bool = False,
393-
from_all_courses: bool = False, exercise_tag: Optional[str] = None,
394-
):
395-
user = User.get(User.id == user_id)
396-
exercises = (
400+
def by_user(cls, user_id: int):
401+
return (
397402
cls
398403
.select()
399404
.join(Course)
@@ -402,17 +407,20 @@ def get_objects(
402407
.switch()
403408
.order_by(UserCourse.date, Exercise.number, Exercise.order)
404409
)
410+
411+
@classmethod
412+
def get_objects(
413+
cls, user_id: int, fetch_archived: bool = False,
414+
from_all_courses: bool = False, exercise_tag: Optional[str] = None,
415+
):
416+
user = User.get(User.id == user_id)
417+
exercises = cls.by_user(user_id)
405418
if not from_all_courses:
406419
exercises = exercises.where(
407420
UserCourse.course == user.last_course_viewed,
408421
)
409422
if exercise_tag:
410-
exercises = (
411-
exercises
412-
.join(ExerciseTag)
413-
.join(ExerciseTagText)
414-
.where(ExerciseTagText.text == exercise_tag)
415-
)
423+
exercises = Exercise.by_tags(exercises, exercise_tag)
416424
if not fetch_archived:
417425
exercises = exercises.where(cls.is_archived == False) # NOQA: E712
418426
return exercises
@@ -434,6 +442,15 @@ def as_dict(self) -> Dict[str, Any]:
434442
def as_dicts(exercises: Iterable['Exercise']) -> ExercisesDictById:
435443
return {exercise.id: exercise.as_dict() for exercise in exercises}
436444

445+
@staticmethod
446+
def by_tags(exercises: Iterable['Exercise'], exercise_tag: str):
447+
return (
448+
exercises
449+
.join(ExerciseTag)
450+
.join(Tag)
451+
.where(Tag.text == exercise_tag)
452+
)
453+
437454
def __str__(self):
438455
return self.subject
439456

@@ -448,7 +465,7 @@ def exercise_number_save_handler(model_class, instance, created):
448465

449466
class ExerciseTag(BaseModel):
450467
exercise = ForeignKeyField(Exercise)
451-
tag = ForeignKeyField(ExerciseTagText)
468+
tag = ForeignKeyField(Tag)
452469
date = DateTimeField(default=datetime.now)
453470

454471
@classmethod
@@ -467,8 +484,8 @@ def is_course_tag_exists(cls, course: Course, tag_name: str):
467484
return (
468485
cls
469486
.select()
470-
.join(ExerciseTagText)
471-
.where(ExerciseTagText.text == tag_name)
487+
.join(Tag)
488+
.where(Tag.text == tag_name)
472489
.switch()
473490
.join(Exercise)
474491
.where(Exercise.course == course)

lms/lmsweb/views.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,25 @@ def change_last_course_viewed(course_id: int):
343343

344344

345345
@webapp.route('/exercises')
346+
@login_required
347+
def exercises_page():
348+
fetch_archived = bool(request.args.get('archived'))
349+
exercises = Solution.of_user(current_user.id, fetch_archived)
350+
is_manager = current_user.role.is_manager
351+
return render_template(
352+
'exercises.html',
353+
exercises=exercises,
354+
is_manager=is_manager,
355+
fetch_archived=fetch_archived,
356+
)
357+
358+
346359
@webapp.route('/exercises/<tag_name>')
347360
@login_required
348-
def exercises_page(tag_name: Optional[str] = None):
361+
def exercises_tag_page(tag_name: str):
349362
fetch_archived = bool(request.args.get('archived'))
350363
try:
351-
solutions.check_tag_name(tag_name)
364+
solutions.check_tag_name(tag_name, current_user.last_course_viewed)
352365
except LmsError as e:
353366
error_message, status_code = e.args
354367
return fail(status_code, error_message)

lms/models/solutions.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99

1010
from lms.extractors.base import File
1111
from lms.lmsdb.models import (
12-
ExerciseTag, SharedSolution, Solution, SolutionFile, User,
12+
Course, SharedSolution, Solution, SolutionFile, User,
1313
)
1414
from lms.lmstests.public.general import tasks as general_tasks
1515
from lms.lmstests.public.identical_tests import tasks as identical_tests_tasks
1616
from lms.lmsweb import config, routes
17-
from lms.models import comments, notifications
17+
from lms.models import comments, notifications, tags
1818
from lms.models.errors import ForbiddenPermission, ResourceNotFound
1919
from lms.utils.files import ALLOWED_IMAGES_EXTENSIONS
2020

@@ -209,12 +209,8 @@ def get_files_tree(files: Iterable[SolutionFile]) -> List[Dict[str, Any]]:
209209
return file_details
210210

211211

212-
def check_tag_name(tag_name: Optional[str]) -> None:
213-
if (
214-
tag_name is not None and not ExerciseTag.is_course_tag_exists(
215-
current_user.last_course_viewed, tag_name,
216-
)
217-
):
212+
def check_tag_name(tag_name: str, course: Course) -> None:
213+
if not tags.get_exercises_of(course, tag_name):
218214
raise ResourceNotFound(
219215
f'No such tag {tag_name} for course '
220216
f'{current_user.last_course_viewed.name}.', 404,

lms/models/tags.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import Iterable, Optional, Union
2+
3+
from lms.lmsdb.models import Course, Exercise, ExerciseTag, Tag
4+
5+
6+
def get_exercises_of(
7+
course: Course, tag_name: str,
8+
) -> Union[Iterable['ExerciseTag'], 'ExerciseTag']:
9+
return (
10+
ExerciseTag
11+
.select(ExerciseTag.exercise)
12+
.join(Tag)
13+
.where(Tag.text == tag_name, Tag.course == course)
14+
)
15+
16+
17+
def of_exercise(
18+
exercise_id: Optional[int] = None, course: Optional[int] = None,
19+
number: Optional[int] = None,
20+
) -> Optional[Union[Iterable['ExerciseTag'], 'ExerciseTag']]:
21+
if exercise_id is not None:
22+
return ExerciseTag.select().where(ExerciseTag.exercise == id)
23+
elif course is not None:
24+
tags = (
25+
ExerciseTag
26+
.select()
27+
.join(Exercise)
28+
.where(Exercise.course == course)
29+
)
30+
if number is not None:
31+
tags = tags.where(Exercise.number == number)
32+
return tags
33+
return None

lms/static/my.css

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,6 @@ a {
187187
white-space: normal;
188188
}
189189

190-
#exercise-tag-link {
191-
color: #919191;
192-
}
193-
194-
#exercise-tag-link:hover {
195-
color: #646464;
196-
}
197-
198190
.exercise-send {
199191
display: flex;
200192
flex-direction: row;

lms/templates/exercises.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div id="exercises-page" class="page {{ direction }}">
66
<div id="exercises-header">
77
<div id="main-title">
8-
<h1 id="exercises-head">{{ _('Exercises') }}{% if tag_name %} - #{{ tag_name }}{% endif %}</h1>
8+
<h1 id="exercises-head">{{ _('Exercises') }}{% if tag_name %} - #{{ tag_name | e }}{% endif %}</h1>
99
</div>
1010
</div>
1111
<div id="exercises">
@@ -16,7 +16,7 @@ <h1 id="exercises-head">{{ _('Exercises') }}{% if tag_name %} - #{{ tag_name }}{
1616
<div class="exercise-name"><div class="ex-title">{{ exercise['exercise_name'] | e }}</div></div>
1717
<div class="exercise-tags ms-1">
1818
{% for tag in exercise.tags %}
19-
<a class="ms-1" id="exercise-tag-link" href="{{ url_for('exercises_page', tag_name=tag.tag) }}">#{{ tag.tag }}</a>
19+
<a class="ms-1" id="exercise-tag-link" href="{{ url_for('exercises_tag_page', tag_name=tag.tag) }}">#{{ tag.tag | e }}</a>
2020
{% endfor %}
2121
</div>
2222
</div>

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from lms.lmsdb.models import (
1818
ALL_MODELS, Comment, CommentText, Course, Exercise, ExerciseTag,
19-
ExerciseTagText, Note, Notification, Role, RoleOptions, SharedSolution,
19+
Tag, Note, Notification, Role, RoleOptions, SharedSolution,
2020
Solution, User, UserCourse,
2121
)
2222
from lms.extractors.base import File
@@ -309,8 +309,8 @@ def create_exercise(
309309
)
310310

311311

312-
def create_exercise_tag(tag_text: str, exercise: Exercise):
313-
new_tag_id = ExerciseTagText.create_tag(text=tag_text).id
312+
def create_exercise_tag(tag_text: str, course: Course, exercise: Exercise):
313+
new_tag_id = Tag.create_tag(text=tag_text, course=course).id
314314
return ExerciseTag.create(exercise=exercise, tag=new_tag_id)
315315

316316

tests/test_exercises.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import datetime
22

33
from lms.lmsdb.models import Course, Exercise, User
4+
from lms.models import tags
45
from tests import conftest
56

67

@@ -60,14 +61,14 @@ def test_exercise_tags(
6061
client = conftest.get_logged_user(username=student_user.username)
6162
conftest.create_usercourse(student_user, course)
6263
client.get(f'course/{course.id}')
63-
conftest.create_exercise_tag('tag1', exercise)
64+
conftest.create_exercise_tag('tag1', course, exercise)
6465
tag_response = client.get('/exercises/tag1')
6566
assert tag_response.status_code == 200
6667

6768
course2 = conftest.create_course(index=1)
6869
exercise2 = conftest.create_exercise(course2, 2)
6970
conftest.create_usercourse(student_user, course2)
70-
conftest.create_exercise_tag('tag2', exercise2)
71+
conftest.create_exercise_tag('tag2', course2, exercise2)
7172
bad_tag_response = client.get('/exercises/tag2')
7273
assert bad_tag_response.status_code == 404
7374

@@ -77,3 +78,13 @@ def test_exercise_tags(
7778

7879
another_bad_tag_response = client.get('/exercises/wrongtag')
7980
assert another_bad_tag_response.status_code == 404
81+
82+
@staticmethod
83+
def test_course_tags(course: Course, exercise: Exercise):
84+
course2 = conftest.create_course(index=1)
85+
conftest.create_exercise_tag('tag1', course, exercise)
86+
conftest.create_exercise_tag('tag2', course, exercise)
87+
exercise2 = conftest.create_exercise(course, 2)
88+
conftest.create_exercise_tag('tag1', course, exercise2)
89+
assert len(tags.of_exercise(course=course.id)) == 3
90+
assert len(tags.of_exercise(course=course2.id)) == 0

0 commit comments

Comments
 (0)