Skip to content

Commit 5cda34f

Browse files
authored
LMS app massive changes (#2754)
* lms API: Displaying lesson count in the modules * lms API: hiding modules that has not been started yet * lms admin: module archiving, various speedups * question admin: filtering by course Some code revamp in the `lms` app: * Fixed ModuleViewSet name * lms app serializers split
1 parent d493a9f commit 5cda34f

File tree

27 files changed

+558
-199
lines changed

27 files changed

+558
-199
lines changed

src/apps/homework/admin/question/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class QuestionAdmin(ModelAdmin):
1919
"product",
2020
"tariff",
2121
]
22+
list_filter = [
23+
"lesson__module__course__group",
24+
]
2225
fields = [
2326
"module",
2427
"lesson",

src/apps/homework/admin/question/form.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
class QuestionForm(ModelForm):
13-
module = forms.ModelChoiceField(label=_("Module"), queryset=Module.objects.for_admin(), required=False)
13+
module = forms.ModelChoiceField(label=_("Module"), queryset=Module.objects.for_admin().exclude(archived=True), required=False)
1414
lesson = forms.ModelChoiceField(label=_("Lesson"), queryset=Lesson.objects.for_admin(), required=False)
1515

1616
class Meta:

src/apps/homework/api/serializers/question.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44

55
from apps.homework.api.serializers.stats import HomeworkStatsSerializer
66
from apps.homework.models import Question
7+
from apps.products.models import Course
8+
from core.serializers import MarkdownField
9+
10+
11+
class QuestionCourseSerializer(serializers.ModelSerializer):
12+
homework_check_recommendations = MarkdownField()
13+
14+
class Meta:
15+
model = Course
16+
fields = [
17+
"id",
18+
"slug",
19+
"name",
20+
"cover",
21+
"chat",
22+
"calendar_ios",
23+
"calendar_google",
24+
"homework_check_recommendations",
25+
]
726

827

928
class QuestionSerializer(serializers.ModelSerializer):
@@ -37,16 +56,14 @@ class Meta:
3756
"course",
3857
]
3958

40-
@extend_schema_field(lazy_serializer("apps.lms.api.serializers.LMSCourseSerializer")())
59+
@extend_schema_field(QuestionCourseSerializer)
4160
def get_course(self, question: Question) -> dict:
42-
from apps.lms.api.serializers import LMSCourseSerializer
43-
4461
course = question.get_course(user=self.context["request"].user)
4562

4663
if course is None:
4764
course = question.get_legacy_course()
4865

49-
return LMSCourseSerializer(course).data
66+
return QuestionCourseSerializer(course).data
5067

5168
@extend_schema_field(lazy_serializer("apps.lms.api.serializers.BreadcrumbsSerializer")())
5269
def get_breadcrumbs(self, question: Question) -> dict | None:

src/apps/homework/factory.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55

66
@register
7-
def question(self: FixtureFactory, course: Course | None = None, name: str | None = None, **kwargs: dict) -> Question:
7+
def question(
8+
self: FixtureFactory,
9+
course: Course | None = None,
10+
name: str | None = None,
11+
**kwargs: dict,
12+
) -> Question:
813
question = Question.objects.create(
914
name=name or f"Please {self.faker.bs()} two times",
1015
**kwargs,

src/apps/lms/admin/lesson/admin.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,18 @@ class LessonAdmin(ModelAdmin):
3434
}
3535

3636
list_display = [
37-
"name",
37+
"id",
3838
"course_name",
3939
"module_name",
4040
"material_title",
4141
"question_name",
4242
]
4343

44+
list_display_links = [
45+
"id",
46+
"course_name",
47+
]
48+
4449
class Media:
4550
js = ["admin/js/vendor/jquery/jquery.js", "admin/add_material_link.js", "admin/remove_call_select.js"]
4651
css = {
@@ -75,10 +80,6 @@ def question_name(self, lesson: Lesson) -> str:
7580

7681
return "—"
7782

78-
@admin.display(description=_("Name"))
79-
def name(self, lesson: Lesson) -> str:
80-
return str(lesson)
81-
8283
def has_add_permission(self, request: HttpRequest) -> bool:
8384
return False
8485

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from typing import Any
2+
3+
from django.contrib.admin.models import CHANGE
4+
from django.db.models import QuerySet
5+
from django.http import HttpRequest
6+
from django.utils.translation import gettext_lazy as _
7+
8+
from apps.lms.models import Module
9+
from core.admin import admin
10+
from core.tasks import write_admin_log
11+
12+
13+
@admin.action(description=_("Put to archive"))
14+
def archive(modeladmin: Any, request: HttpRequest, queryset: QuerySet[Module]) -> None:
15+
for module in queryset.iterator():
16+
write_admin_log.delay(
17+
action_flag=CHANGE,
18+
app="lms",
19+
model="Module",
20+
change_message="Module archived (bulk action)",
21+
object_id=module.id,
22+
user_id=request.user.id,
23+
)
24+
25+
count = queryset.update(archived=True)
26+
27+
modeladmin.message_user(request, f"{count} modules archived")
28+
29+
30+
@admin.action(description=_("Extract from archive"))
31+
def unarchive(modeladmin: Any, request: HttpRequest, queryset: QuerySet[Module]) -> None:
32+
for module in queryset.iterator():
33+
write_admin_log.delay(
34+
action_flag=CHANGE,
35+
app="lms",
36+
model="Module",
37+
change_message="Module unarchived (bulk action)",
38+
object_id=module.id,
39+
user_id=request.user.id,
40+
)
41+
42+
count = queryset.update(archived=False)
43+
44+
modeladmin.message_user(request, f"{count} modules unarchived")

src/apps/lms/admin/module/admin.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,22 @@
44
from django.utils.translation import gettext_lazy as _
55

66
from apps.lms.admin.lesson.inline import LessonInline
7+
from apps.lms.admin.module import actions
78
from apps.lms.models import Module
89
from apps.products.admin.filters import CourseFilter
910
from core.admin import ModelAdmin, admin
11+
from core.admin.filters import DefaultFalseBooleanFilter
12+
13+
14+
class Archived(DefaultFalseBooleanFilter):
15+
title = _("Archived")
16+
parameter_name = "archived"
17+
18+
def t(self, request: HttpRequest, queryset: QuerySet[Module]) -> QuerySet:
19+
return queryset.filter(archived=True)
20+
21+
def f(self, request: HttpRequest, queryset: QuerySet[Module]) -> QuerySet:
22+
return queryset.filter(archived=False)
1023

1124

1225
@admin.register(Module)
@@ -15,6 +28,7 @@ class ModuleAdmin(SortableAdminBase, ModelAdmin):
1528
"name",
1629
"start_date",
1730
"course",
31+
"archived",
1832
"description",
1933
"text",
2034
]
@@ -27,12 +41,17 @@ class ModuleAdmin(SortableAdminBase, ModelAdmin):
2741
"lesson_count",
2842
]
2943
list_filter = [
44+
Archived,
3045
CourseFilter,
3146
]
3247

3348
inlines = [
3449
LessonInline,
3550
]
51+
actions = [
52+
actions.archive,
53+
actions.unarchive,
54+
]
3655

3756
class Media:
3857
js = ["admin/js/vendor/jquery/jquery.js", "admin/autoset-hidden-fiend-during-lesson-adding.js"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from apps.lms.api.serializers.breadcrums import BreadcrumbsSerializer
2+
from apps.lms.api.serializers.lesson import LessonSerializer
3+
from apps.lms.api.serializers.module import ModuleDetailSerializer, ModuleSerializer
4+
5+
__all__ = [
6+
"BreadcrumbsSerializer",
7+
"LessonSerializer",
8+
"ModuleDetailSerializer",
9+
"ModuleSerializer",
10+
]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from drf_spectacular.utils import extend_schema_field, inline_serializer
2+
from rest_framework import serializers
3+
4+
from apps.lms.api.serializers.module import ModuleSerializer
5+
from apps.lms.models import Course, Lesson
6+
from core.serializers import MarkdownField
7+
8+
9+
class LMSCourseSerializer(serializers.ModelSerializer):
10+
homework_check_recommendations = MarkdownField()
11+
12+
class Meta:
13+
model = Course
14+
fields = [
15+
"id",
16+
"slug",
17+
"name",
18+
"cover",
19+
"chat",
20+
"calendar_ios",
21+
"calendar_google",
22+
"homework_check_recommendations",
23+
]
24+
25+
26+
class BreadcrumbsSerializer(serializers.ModelSerializer):
27+
module = ModuleSerializer()
28+
course = LMSCourseSerializer(source="module.course")
29+
lesson = serializers.SerializerMethodField()
30+
31+
class Meta:
32+
model = Lesson
33+
fields = [
34+
"module",
35+
"course",
36+
"lesson",
37+
]
38+
39+
@extend_schema_field(
40+
field=inline_serializer(
41+
name="LessonPlainSerializer",
42+
fields={
43+
"id": serializers.IntegerField(),
44+
},
45+
)
46+
)
47+
def get_lesson(self, lesson: Lesson) -> dict:
48+
return {
49+
"id": lesson.id,
50+
}

src/apps/lms/api/serializers.py renamed to src/apps/lms/api/serializers/lesson.py

Lines changed: 2 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from typing import Literal
22

3-
from drf_spectacular.utils import OpenApiExample, extend_schema_field, extend_schema_serializer, inline_serializer
3+
from drf_spectacular.utils import extend_schema_field, inline_serializer
44
from rest_framework import serializers
55

66
from apps.homework.api.serializers import HomeworkStatsSerializer, QuestionSerializer
77
from apps.homework.models import Question
8-
from apps.lms.models import Call, Course, Lesson, Module
8+
from apps.lms.models import Call, Lesson
99
from apps.notion.models import Material as NotionMaterial
10-
from core.serializers import MarkdownField
1110

1211

1312
class NotionMaterialSerializer(serializers.ModelSerializer):
@@ -109,75 +108,3 @@ def get_homework(self, lesson: Lesson) -> dict | None:
109108
question = Question.objects.for_user(user).get(pk=lesson.question_id) # extra N+1 query to annotate the question with statistics
110109

111110
return HomeworkStatsSerializer(question, context=self.context).data
112-
113-
114-
@extend_schema_serializer(
115-
examples=[
116-
OpenApiExample(
117-
name="Markdown in descrpition",
118-
value={
119-
"id": 100500,
120-
"name": "Первая неделя",
121-
"start_date": "2023-12-01 15:30:00+03:00",
122-
"description": "Cамая важная неделя",
123-
"text": "<p><strong>Первая</strong> неделя — <em>самая важная неделя</em></p>",
124-
},
125-
),
126-
]
127-
)
128-
class ModuleSerializer(serializers.ModelSerializer):
129-
text = MarkdownField()
130-
131-
class Meta:
132-
model = Module
133-
fields = [
134-
"id",
135-
"name",
136-
"start_date",
137-
"description",
138-
"text",
139-
]
140-
141-
142-
class LMSCourseSerializer(serializers.ModelSerializer):
143-
homework_check_recommendations = MarkdownField()
144-
145-
class Meta:
146-
model = Course
147-
fields = [
148-
"id",
149-
"slug",
150-
"name",
151-
"cover",
152-
"chat",
153-
"calendar_ios",
154-
"calendar_google",
155-
"homework_check_recommendations",
156-
]
157-
158-
159-
class BreadcrumbsSerializer(serializers.ModelSerializer):
160-
module = ModuleSerializer()
161-
course = LMSCourseSerializer(source="module.course")
162-
lesson = serializers.SerializerMethodField()
163-
164-
class Meta:
165-
model = Lesson
166-
fields = [
167-
"module",
168-
"course",
169-
"lesson",
170-
]
171-
172-
@extend_schema_field(
173-
field=inline_serializer(
174-
name="LessonPlainSerializer",
175-
fields={
176-
"id": serializers.IntegerField(),
177-
},
178-
)
179-
)
180-
def get_lesson(self, lesson: Lesson) -> dict:
181-
return {
182-
"id": lesson.id,
183-
}

0 commit comments

Comments
 (0)