|
|
@ -1,4 +1,6 @@ |
|
|
import logging |
|
|
import logging |
|
|
|
|
|
import re |
|
|
|
|
|
from copy import deepcopy |
|
|
|
|
|
|
|
|
from django.db import transaction |
|
|
from django.db import transaction |
|
|
from django.db.models import Count, Q |
|
|
from django.db.models import Count, Q |
|
|
@ -27,6 +29,8 @@ from apps.course.models import ( |
|
|
LiveSessionUser, |
|
|
LiveSessionUser, |
|
|
LiveSessionRecording |
|
|
LiveSessionRecording |
|
|
) |
|
|
) |
|
|
|
|
|
from apps.course.models.course import extract_text_from_json |
|
|
|
|
|
from apps.quiz.models import Quiz, Question |
|
|
from apps.course.serializers.admin import ( |
|
|
from apps.course.serializers.admin import ( |
|
|
AdminCourseCategorySerializer, |
|
|
AdminCourseCategorySerializer, |
|
|
AdminCourseListSerializer, |
|
|
AdminCourseListSerializer, |
|
|
@ -134,6 +138,156 @@ class AdminCourseViewSet(viewsets.ModelViewSet): |
|
|
else: |
|
|
else: |
|
|
serializer.save() |
|
|
serializer.save() |
|
|
|
|
|
|
|
|
|
|
|
def _build_cloned_title(self, source_course, requested_title=None): |
|
|
|
|
|
custom_title = str(requested_title or "").strip() |
|
|
|
|
|
if custom_title: |
|
|
|
|
|
return [{"language_code": "en", "title": custom_title}] |
|
|
|
|
|
|
|
|
|
|
|
source_titles = deepcopy(source_course.title or []) |
|
|
|
|
|
source_title_text = extract_text_from_json(source_titles).strip() or f"Course {source_course.pk}" |
|
|
|
|
|
base_title = re.sub(r"\s*\(copied(?:#\d+)?\)$", "", source_title_text).strip() or source_title_text |
|
|
|
|
|
suffix = "(copied)" |
|
|
|
|
|
|
|
|
|
|
|
if isinstance(source_titles, list) and source_titles: |
|
|
|
|
|
localized_titles = [] |
|
|
|
|
|
for item in source_titles: |
|
|
|
|
|
if isinstance(item, dict): |
|
|
|
|
|
localized_item = deepcopy(item) |
|
|
|
|
|
for key in ("title", "text", "value", "name"): |
|
|
|
|
|
if key in localized_item and localized_item.get(key): |
|
|
|
|
|
cleaned_value = re.sub(r"\s*\(copied(?:#\d+)?\)$", "", str(localized_item[key])).strip() |
|
|
|
|
|
localized_item[key] = f"{cleaned_value}{suffix}" |
|
|
|
|
|
break |
|
|
|
|
|
else: |
|
|
|
|
|
localized_item["title"] = f"{base_title}{suffix}" |
|
|
|
|
|
localized_titles.append(localized_item) |
|
|
|
|
|
elif item: |
|
|
|
|
|
cleaned_item = re.sub(r"\s*\(copied(?:#\d+)?\)$", "", str(item)).strip() |
|
|
|
|
|
localized_titles.append(f"{cleaned_item}{suffix}") |
|
|
|
|
|
|
|
|
|
|
|
if localized_titles: |
|
|
|
|
|
return localized_titles |
|
|
|
|
|
|
|
|
|
|
|
return [{"language_code": "en", "title": f"{base_title}{suffix}"}] |
|
|
|
|
|
|
|
|
|
|
|
@action(detail=True, methods=['post']) |
|
|
|
|
|
def clone(self, request, pk=None): |
|
|
|
|
|
source_course = self.get_object() |
|
|
|
|
|
requested_title = request.data.get('title') |
|
|
|
|
|
|
|
|
|
|
|
with transaction.atomic(): |
|
|
|
|
|
cloned_course = Course.objects.create( |
|
|
|
|
|
title=self._build_cloned_title(source_course, requested_title), |
|
|
|
|
|
category=source_course.category, |
|
|
|
|
|
thumbnail=source_course.thumbnail.name if source_course.thumbnail else None, |
|
|
|
|
|
video_type=source_course.video_type, |
|
|
|
|
|
video_file=source_course.video_file.name if source_course.video_file else None, |
|
|
|
|
|
video_link=source_course.video_link, |
|
|
|
|
|
is_online=source_course.is_online, |
|
|
|
|
|
online_link=source_course.online_link, |
|
|
|
|
|
level=deepcopy(source_course.level), |
|
|
|
|
|
duration=source_course.duration, |
|
|
|
|
|
lessons_count=source_course.lessons_count, |
|
|
|
|
|
description=deepcopy(source_course.description), |
|
|
|
|
|
short_description=deepcopy(source_course.short_description), |
|
|
|
|
|
status=deepcopy(source_course.status), |
|
|
|
|
|
is_free=source_course.is_free, |
|
|
|
|
|
price=source_course.price, |
|
|
|
|
|
price_rub=source_course.price_rub, |
|
|
|
|
|
discount_percentage=source_course.discount_percentage, |
|
|
|
|
|
is_group_chat_locked=source_course.is_group_chat_locked, |
|
|
|
|
|
is_professor_chat_locked=source_course.is_professor_chat_locked, |
|
|
|
|
|
timing=deepcopy(source_course.timing), |
|
|
|
|
|
features=deepcopy(source_course.features), |
|
|
|
|
|
) |
|
|
|
|
|
cloned_course.professors.set(source_course.professors.all()) |
|
|
|
|
|
|
|
|
|
|
|
chapter_map = {} |
|
|
|
|
|
source_chapters = source_course.chapters.all().order_by('priority', 'id') |
|
|
|
|
|
for chapter in source_chapters: |
|
|
|
|
|
cloned_chapter = CourseChapter.objects.create( |
|
|
|
|
|
course=cloned_course, |
|
|
|
|
|
title=deepcopy(chapter.title), |
|
|
|
|
|
priority=chapter.priority, |
|
|
|
|
|
is_active=chapter.is_active, |
|
|
|
|
|
) |
|
|
|
|
|
chapter_map[chapter.id] = cloned_chapter |
|
|
|
|
|
|
|
|
|
|
|
lesson_map = {} |
|
|
|
|
|
source_lessons = source_course.lessons.select_related('lesson', 'chapter').all().order_by('priority', 'id') |
|
|
|
|
|
for course_lesson in source_lessons: |
|
|
|
|
|
base_lesson = course_lesson.lesson |
|
|
|
|
|
cloned_lesson = Lesson.objects.create( |
|
|
|
|
|
title=deepcopy(base_lesson.title), |
|
|
|
|
|
content_type=deepcopy(base_lesson.content_type), |
|
|
|
|
|
content_file=base_lesson.content_file.name if base_lesson and base_lesson.content_file else None, |
|
|
|
|
|
video_link=base_lesson.video_link if base_lesson else None, |
|
|
|
|
|
duration=base_lesson.duration if base_lesson else 0, |
|
|
|
|
|
) |
|
|
|
|
|
cloned_course_lesson = CourseLesson.objects.create( |
|
|
|
|
|
course=cloned_course, |
|
|
|
|
|
chapter=chapter_map.get(course_lesson.chapter_id), |
|
|
|
|
|
lesson=cloned_lesson, |
|
|
|
|
|
title=deepcopy(course_lesson.title), |
|
|
|
|
|
priority=course_lesson.priority, |
|
|
|
|
|
is_active=course_lesson.is_active, |
|
|
|
|
|
) |
|
|
|
|
|
lesson_map[course_lesson.id] = cloned_course_lesson |
|
|
|
|
|
|
|
|
|
|
|
source_attachments = source_course.attachments.select_related('attachment').all() |
|
|
|
|
|
for course_attachment in source_attachments: |
|
|
|
|
|
base_attachment = course_attachment.attachment |
|
|
|
|
|
cloned_attachment = Attachment.objects.create( |
|
|
|
|
|
title=deepcopy(base_attachment.title), |
|
|
|
|
|
file=base_attachment.file.name if base_attachment and base_attachment.file else None, |
|
|
|
|
|
file_size=base_attachment.file_size if base_attachment else None, |
|
|
|
|
|
) |
|
|
|
|
|
CourseAttachment.objects.create( |
|
|
|
|
|
course=cloned_course, |
|
|
|
|
|
attachment=cloned_attachment, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
source_glossaries = source_course.glossaries.select_related('glossary').all() |
|
|
|
|
|
for course_glossary in source_glossaries: |
|
|
|
|
|
base_glossary = course_glossary.glossary |
|
|
|
|
|
cloned_glossary = Glossary.objects.create( |
|
|
|
|
|
title=base_glossary.title, |
|
|
|
|
|
description=deepcopy(base_glossary.description), |
|
|
|
|
|
) |
|
|
|
|
|
CourseGlossary.objects.create( |
|
|
|
|
|
course=cloned_course, |
|
|
|
|
|
glossary=cloned_glossary, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
source_quizzes = source_course.quizzes.prefetch_related('questions').all() |
|
|
|
|
|
for quiz in source_quizzes: |
|
|
|
|
|
cloned_quiz = Quiz.objects.create( |
|
|
|
|
|
lesson=lesson_map.get(quiz.lesson_id), |
|
|
|
|
|
course=cloned_course, |
|
|
|
|
|
lesson_number=quiz.lesson_number, |
|
|
|
|
|
title=deepcopy(quiz.title), |
|
|
|
|
|
description=deepcopy(quiz.description), |
|
|
|
|
|
each_question_timing=quiz.each_question_timing, |
|
|
|
|
|
status=quiz.status, |
|
|
|
|
|
) |
|
|
|
|
|
for question in quiz.questions.all(): |
|
|
|
|
|
Question.objects.create( |
|
|
|
|
|
quiz=cloned_quiz, |
|
|
|
|
|
question=deepcopy(question.question), |
|
|
|
|
|
option1=deepcopy(question.option1), |
|
|
|
|
|
option2=deepcopy(question.option2), |
|
|
|
|
|
option3=deepcopy(question.option3), |
|
|
|
|
|
option4=deepcopy(question.option4), |
|
|
|
|
|
correct_answer=question.correct_answer, |
|
|
|
|
|
priority=question.priority, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
cloned_course.recalculate_lessons_count() |
|
|
|
|
|
cloned_course.refresh_from_db() |
|
|
|
|
|
|
|
|
|
|
|
serializer = self.get_serializer(cloned_course) |
|
|
|
|
|
return Response(serializer.data, status=201) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminChapterViewSet(viewsets.ModelViewSet): |
|
|
class AdminChapterViewSet(viewsets.ModelViewSet): |
|
|
""" |
|
|
""" |
|
|
|