Skip to content
Merged

Dev #611

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ddca179
Создан модуль Курсы
Toksi86 Feb 20, 2026
9d56beb
Создана модель курса
Toksi86 Feb 20, 2026
b7102f9
Создана модель Модуля
Toksi86 Feb 20, 2026
dc449cc
Добавлена модель урока
Toksi86 Feb 20, 2026
34a1c34
Созданы модели вопросов и ответов
Toksi86 Feb 25, 2026
6a95d88
Модели докомпозиорованы, убран автопересчёт порядков
Toksi86 Feb 25, 2026
1c14f44
Новые модели добавлены в панель администратора, протестировано ручное…
Toksi86 Feb 26, 2026
526cace
Добавлены модели прогрессов с ограничениями на уровне БД
Toksi86 Mar 3, 2026
f67f96f
Ужесточена валидация публикации choice-заданий, синхронизировано поле…
Toksi86 Mar 3, 2026
9ec6eb1
реализованы сервисы и API курсов (каталог, структура, уроки, submit о…
Toksi86 Mar 6, 2026
b14a95d
Забытый файл из коммита 9ec6eb1
Toksi86 Mar 6, 2026
db83a2c
удалён пустой файл с неиспользуемым импортом
Toksi86 Mar 6, 2026
fbba784
Merge pull request #606 from PROCOLLAB-github/feature/program-courses
Toksi86 Mar 6, 2026
6d9d18b
Добавлены task_count для уроков и avatar_url для модулей в структуре …
Toksi86 Mar 10, 2026
90ccb89
Merge pull request #607 from PROCOLLAB-github/feature/program-courses
Toksi86 Mar 10, 2026
a66c3e8
Добавлены partner_program_id в API курсов и валидация изображений для…
Toksi86 Mar 11, 2026
3822faa
Merge pull request #608 from PROCOLLAB-github/feature/program-courses
Toksi86 Mar 11, 2026
c1be08d
Обновлена логика прохождения информационных заданий и агрегированного…
Toksi86 Mar 12, 2026
99b492c
refactor(courses): разделены query/api/admin слои, упрощены сервисы п…
Toksi86 Mar 13, 2026
3931727
Добавлен lesson_detail в query-слой модуля courses после рефакторинга
Toksi86 Mar 13, 2026
57be27b
Merge pull request #609 from PROCOLLAB-github/feature/program-courses
Toksi86 Mar 13, 2026
ab57826
Добавлены answer_title для заданий и module_order в lesson detail API…
Toksi86 Mar 13, 2026
e02a7d3
Merge pull request #610 from PROCOLLAB-github/feature/program-courses
Toksi86 Mar 13, 2026
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
Empty file added courses/__init__.py
Empty file.
1 change: 1 addition & 0 deletions courses/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from courses.admin_config import * # noqa: F401,F403
1 change: 1 addition & 0 deletions courses/admin_config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import answers, content, progress, site # noqa: F401
77 changes: 77 additions & 0 deletions courses/admin_config/answers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from django.contrib import admin

from courses.models import UserTaskAnswer, UserTaskAnswerFile, UserTaskAnswerOption

from .inlines import UserTaskAnswerFileInline, UserTaskAnswerOptionInline


@admin.register(UserTaskAnswer)
class UserTaskAnswerAdmin(admin.ModelAdmin):
list_display = (
"id",
"user",
"task",
"status",
"is_correct",
"submitted_at",
"reviewed_by",
"reviewed_at",
)
list_display_links = ("id",)
list_filter = (
"status",
"is_correct",
"task__check_type",
"task__answer_type",
"task__lesson__module__course",
)
search_fields = (
"id",
"user__email",
"user__first_name",
"user__last_name",
"task__title",
)
raw_id_fields = ("user", "task", "reviewed_by")
readonly_fields = ("submitted_at", "datetime_created", "datetime_updated")
list_select_related = (
"user",
"task",
"reviewed_by",
"task__lesson",
"task__lesson__module",
"task__lesson__module__course",
)
inlines = [UserTaskAnswerOptionInline, UserTaskAnswerFileInline]


@admin.register(UserTaskAnswerOption)
class UserTaskAnswerOptionAdmin(admin.ModelAdmin):
list_display = ("id", "answer", "option", "get_user", "get_task")
list_display_links = ("id",)
search_fields = (
"id",
"answer__user__email",
"answer__task__title",
"option__text",
)
raw_id_fields = ("answer", "option")
list_select_related = ("answer", "option", "answer__user", "answer__task")

@admin.display(description="Пользователь", ordering="answer__user")
def get_user(self, obj):
return obj.answer.user

@admin.display(description="Задание", ordering="answer__task")
def get_task(self, obj):
return obj.answer.task


@admin.register(UserTaskAnswerFile)
class UserTaskAnswerFileAdmin(admin.ModelAdmin):
list_display = ("id", "answer", "file", "file_name", "file_size", "datetime_uploaded")
list_display_links = ("id",)
search_fields = ("id", "file_name", "answer__task__title", "answer__user__email")
raw_id_fields = ("answer", "file")
readonly_fields = ("file_name", "file_size", "datetime_uploaded")
list_select_related = ("answer", "file", "answer__user", "answer__task")
267 changes: 267 additions & 0 deletions courses/admin_config/content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
from django.contrib import admin

from courses.models import Course, CourseLesson, CourseModule, CourseTask, CourseTaskOption

from .forms import CourseAdminForm, CourseModuleAdminForm, CourseTaskAdminForm
from .helpers import UserFileUploadAdminMixin
from .inlines import (
CourseLessonInline,
CourseModuleInline,
CourseTaskOptionInline,
)


@admin.register(Course)
class CourseAdmin(UserFileUploadAdminMixin, admin.ModelAdmin):
form = CourseAdminForm
list_display = (
"id",
"title",
"access_type",
"status",
"is_completed",
"start_date",
"end_date",
"partner_program",
"datetime_created",
)
list_display_links = ("id", "title")
list_filter = ("access_type", "status", "is_completed")
search_fields = ("id", "title", "partner_program__name")
raw_id_fields = ("partner_program",)
readonly_fields = ("completed_at", "datetime_created", "datetime_updated")
list_select_related = ("partner_program",)
inlines = [CourseModuleInline]
fieldsets = (
(
None,
{
"fields": (
"title",
"description",
"access_type",
"partner_program",
"status",
"is_completed",
)
},
),
(
"Период",
{"fields": ("start_date", "end_date", "completed_at")},
),
(
"Файлы",
{
"fields": (
"avatar_file",
"avatar_upload",
"card_cover_file",
"card_cover_upload",
"header_cover_file",
"header_cover_upload",
)
},
),
(
"Системные поля",
{"fields": ("datetime_created", "datetime_updated")},
),
)

def save_model(self, request, obj, form, change):
avatar_upload = form.cleaned_data.get("avatar_upload")
if avatar_upload:
obj.avatar_file = self.create_user_file(request, avatar_upload)

card_cover_upload = form.cleaned_data.get("card_cover_upload")
if card_cover_upload:
obj.card_cover_file = self.create_user_file(request, card_cover_upload)

header_cover_upload = form.cleaned_data.get("header_cover_upload")
if header_cover_upload:
obj.header_cover_file = self.create_user_file(request, header_cover_upload)

super().save_model(request, obj, form, change)


@admin.register(CourseModule)
class CourseModuleAdmin(UserFileUploadAdminMixin, admin.ModelAdmin):
form = CourseModuleAdminForm
list_display = (
"id",
"title",
"course",
"status",
"start_date",
"order",
"datetime_created",
)
list_display_links = ("id", "title")
list_filter = ("status", "course")
search_fields = ("id", "title", "course__title")
raw_id_fields = ("course",)
readonly_fields = ("datetime_created", "datetime_updated")
list_select_related = ("course",)
inlines = [CourseLessonInline]
fieldsets = (
(
None,
{
"fields": (
"course",
"title",
"start_date",
"status",
"order",
)
},
),
(
"Файлы",
{"fields": ("avatar_file", "avatar_upload")},
),
(
"Системные поля",
{"fields": ("datetime_created", "datetime_updated")},
),
)

def save_model(self, request, obj, form, change):
avatar_upload = form.cleaned_data.get("avatar_upload")
if avatar_upload:
obj.avatar_file = self.create_user_file(request, avatar_upload)
super().save_model(request, obj, form, change)


@admin.register(CourseLesson)
class CourseLessonAdmin(admin.ModelAdmin):
list_display = (
"id",
"title",
"module",
"get_course",
"status",
"order",
"datetime_created",
)
list_display_links = ("id", "title")
list_filter = ("status", "module__course")
search_fields = ("id", "title", "module__title", "module__course__title")
raw_id_fields = ("module",)
readonly_fields = ("datetime_created", "datetime_updated")
list_select_related = ("module", "module__course")

@admin.display(description="Курс", ordering="module__course__title")
def get_course(self, obj):
return obj.module.course


@admin.register(CourseTask)
class CourseTaskAdmin(UserFileUploadAdminMixin, admin.ModelAdmin):
form = CourseTaskAdminForm
list_display = (
"id",
"title",
"lesson",
"get_module",
"get_course",
"task_kind",
"status",
"question_type",
"answer_type",
"order",
)
list_display_links = ("id", "title")
list_filter = (
"status",
"task_kind",
"check_type",
"question_type",
"answer_type",
"lesson__module__course",
)
search_fields = (
"id",
"title",
"lesson__title",
"lesson__module__title",
"lesson__module__course__title",
)
raw_id_fields = ("lesson",)
readonly_fields = ("datetime_created", "datetime_updated")
list_select_related = ("lesson", "lesson__module", "lesson__module__course")
inlines = [CourseTaskOptionInline]
fieldsets = (
(
None,
{
"fields": (
"lesson",
"title",
"status",
"task_kind",
"order",
)
},
),
(
"Типы задания",
{
"fields": (
"check_type",
"informational_type",
"question_type",
"answer_type",
)
},
),
(
"Контент",
{
"fields": (
"body_text",
"answer_title",
"video_url",
"image_file",
"image_upload",
"attachment_file",
"attachment_upload",
)
},
),
(
"Системные поля",
{"fields": ("datetime_created", "datetime_updated")},
),
)

def save_model(self, request, obj, form, change):
image_upload = form.cleaned_data.get("image_upload")
if image_upload:
obj.image_file = self.create_user_file(request, image_upload)

attachment_upload = form.cleaned_data.get("attachment_upload")
if attachment_upload:
obj.attachment_file = self.create_user_file(request, attachment_upload)

super().save_model(request, obj, form, change)

@admin.display(description="Модуль", ordering="lesson__module__title")
def get_module(self, obj):
return obj.lesson.module

@admin.display(description="Курс", ordering="lesson__module__course__title")
def get_course(self, obj):
return obj.lesson.module.course


@admin.register(CourseTaskOption)
class CourseTaskOptionAdmin(admin.ModelAdmin):
list_display = ("id", "task", "text", "is_correct", "order", "datetime_created")
list_display_links = ("id", "text")
list_filter = ("is_correct", "task__answer_type")
search_fields = ("id", "text", "task__title")
raw_id_fields = ("task",)
readonly_fields = ("datetime_created", "datetime_updated")
list_select_related = ("task",)
Loading
Loading