diff --git a/apps/questions/migrations/0015_alter_answersingle_text.py b/apps/questions/migrations/0015_alter_answersingle_text.py new file mode 100644 index 0000000..994999d --- /dev/null +++ b/apps/questions/migrations/0015_alter_answersingle_text.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.3 on 2025-06-30 10:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("questions", "0014_alter_questionconnect_attempts_after_hint_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="answersingle", + name="text", + field=models.CharField(max_length=150), + ), + ] diff --git a/apps/questions/models/answers.py b/apps/questions/models/answers.py index 8cf3ed0..d4f8088 100644 --- a/apps/questions/models/answers.py +++ b/apps/questions/models/answers.py @@ -3,12 +3,16 @@ from files.models import FileModel from progress.models import UserProfile -from questions.models.questions import (QuestionConnect, QuestionSingleAnswer, - QuestionWrite) +from questions.models.questions import ( + QuestionConnect, + QuestionSingleAnswer, + QuestionWrite, +) class AnswerSingle(models.Model): - text = models.CharField(max_length=100, null=False) + # Task изменить максимальную длину строки до 200 символов + text = models.CharField(max_length=150, null=False) is_correct = models.BooleanField(default=False) question = models.ForeignKey( QuestionSingleAnswer, @@ -22,8 +26,12 @@ class Meta: class AnswerConnect(models.Model): - connect_left = models.TextField(max_length=450, null=True, blank=True, verbose_name="Вопрос (текст)") - connect_right = models.TextField(max_length=450, null=True, blank=True, verbose_name="Ответ (текст)") + connect_left = models.TextField( + max_length=450, null=True, blank=True, verbose_name="Вопрос (текст)" + ) + connect_right = models.TextField( + max_length=450, null=True, blank=True, verbose_name="Ответ (текст)" + ) file_left = models.ForeignKey( FileModel, on_delete=models.PROTECT, @@ -53,11 +61,17 @@ class Meta: def clean(self): """Проверка на заполненность полей.""" if not (self.connect_left or self.file_left): - raise ValidationError("Необходимо заполнить хотя бы одно из полей 'Вопрос'.") + raise ValidationError( + "Необходимо заполнить хотя бы одно из полей 'Вопрос'." + ) if not (self.connect_right or self.file_right): raise ValidationError("Необходимо заполнить хотя бы одно из полей 'Ответ'.") - if (self.connect_left and self.file_left) or (self.connect_right and self.file_right): - raise ValidationError("Заполните только одно из полей 'Вопрос' или 'Ответ'.") + if (self.connect_left and self.file_left) or ( + self.connect_right and self.file_right + ): + raise ValidationError( + "Заполните только одно из полей 'Вопрос' или 'Ответ'." + ) def save(self, *args, **kwargs): self.full_clean() diff --git a/apps/questions/views/questions_views.py b/apps/questions/views/questions_views.py index 3107df5..770ed01 100644 --- a/apps/questions/views/questions_views.py +++ b/apps/questions/views/questions_views.py @@ -3,25 +3,36 @@ from django.db.models import QuerySet from drf_spectacular.utils import extend_schema +from progress.models import TaskObjUserResult from rest_framework import generics, status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +from subscription.permissions import SubscriptionTaskObjectPermission -from progress.models import TaskObjUserResult from questions.mapping import TypeQuestionPoints -from questions.models import (AnswerConnect, InfoSlide, QuestionConnect, - QuestionSingleAnswer, QuestionWrite) +from questions.models import ( + AnswerConnect, + InfoSlide, + QuestionConnect, + QuestionSingleAnswer, + QuestionWrite, +) from questions.permissions import CheckQuestionTypePermission -from questions.serializers import (ConnectQuestionSerializer, - InfoSlideSerializer, - SingleQuestionAnswerSerializer, - WriteQuestionSerializer) +from questions.serializers import ( + ConnectQuestionSerializer, + InfoSlideSerializer, + SingleQuestionAnswerSerializer, + WriteQuestionSerializer, +) from questions.services.helpers import add_popup_data -from questions.typing import (AnswerUserWriteData, QuestionSerializerData, - QuestionWriteSerializerData, - QuestionСonnectSerializerData, SingleAnswerData, - SingleConnectedAnswerData) -from subscription.permissions import SubscriptionTaskObjectPermission +from questions.typing import ( + AnswerUserWriteData, + QuestionSerializerData, + QuestionWriteSerializerData, + QuestionСonnectSerializerData, + SingleAnswerData, + SingleConnectedAnswerData, +) class QuestionSingleAnswerGet(generics.RetrieveAPIView): @@ -37,22 +48,28 @@ class QuestionSingleAnswerGet(generics.RetrieveAPIView): summary="Выводит данные для трёх видов вопросов (см. описание)", tags=["Вопросы и инфо-слайд"], description="""для: вопроса с одним правильным ответом, вопроса на исключение, - вопроса на выбор нескольких правильных + вопроса на выбор нескольких правильных ответов (возможно пока его нет, но в будущем может появится).""", ) def get(self, request, *args, **kwargs) -> Response: question: QuestionSingleAnswer = self.request_question - all_answers = question.single_answers.all() - answers = [SingleAnswerData(id=answer.id, text=answer.text) for answer in all_answers] + all_answers = question.single_answers.order_by("id") + + answers = [ + SingleAnswerData(id=answer.id, text=answer.text) for answer in all_answers + ] user_result = TaskObjUserResult.objects.get_answered( - self.task_object_id, self.profile_id, TypeQuestionPoints.QUESTION_SINGLE_ANSWER + self.task_object_id, + self.profile_id, + TypeQuestionPoints.QUESTION_SINGLE_ANSWER, ) - correct_answer = [ - SingleAnswerData(id=all_answers.get(is_correct=True).id, text=all_answers.get(is_correct=True).text) - ] - random.shuffle(answers) + try: + correct = all_answers.get(is_correct=True) + correct_answer = [SingleAnswerData(id=correct.id, text=correct.text)] + except all_answers.model.DoesNotExist: + correct_answer = [] serializer = self.serializer_class( QuestionSerializerData( @@ -61,11 +78,14 @@ def get(self, request, *args, **kwargs) -> Response: description=question.description, files=[file.link for file in question.files.all()], answers=correct_answer if user_result else answers, - is_answered=True if user_result else False, + is_answered=bool(user_result), video_url=question.video_url, ) ) - return Response(add_popup_data(serializer.data, self.request_task_object), status=status.HTTP_200_OK) + return Response( + add_popup_data(serializer.data, self.request_task_object), + status=status.HTTP_200_OK, + ) class QuestionConnectGet(generics.RetrieveAPIView): @@ -117,7 +137,10 @@ def get(self, request, *args, **kwargs): random.shuffle(target_left) serializer = self.serializer_class(question_data) - return Response(add_popup_data(serializer.data, self.request_task_object), status=status.HTTP_200_OK) + return Response( + add_popup_data(serializer.data, self.request_task_object), + status=status.HTTP_200_OK, + ) class QuestionExcludeAnswerGet(generics.RetrieveAPIView): @@ -140,9 +163,13 @@ def get(self, request, *args, **kwargs): question: QuestionSingleAnswer = self.request_question all_answers = question.single_answers.all() - answers = [SingleAnswerData(id=answer.id, text=answer.text) for answer in all_answers] + answers = [ + SingleAnswerData(id=answer.id, text=answer.text) for answer in all_answers + ] answers_to_exclude = [ - SingleAnswerData(id=answer.id, text=answer.text) for answer in all_answers if answer.is_correct + SingleAnswerData(id=answer.id, text=answer.text) + for answer in all_answers + if answer.is_correct ] data_to_serialize = QuestionSerializerData( @@ -162,7 +189,10 @@ def get(self, request, *args, **kwargs): random.shuffle(answers) serializer = self.serializer_class(data_to_serialize) - return Response(add_popup_data(serializer.data, self.request_task_object), status=status.HTTP_200_OK) + return Response( + add_popup_data(serializer.data, self.request_task_object), + status=status.HTTP_200_OK, + ) class InfoSlideDetails(generics.RetrieveAPIView): @@ -188,16 +218,23 @@ def get(self, request, *args, **kwargs): "files": [file.link for file in info_slide.files.all()], "is_done": bool( TaskObjUserResult.objects.get_answered( - self.task_object_id, self.profile_id, TypeQuestionPoints.INFO_SLIDE + self.task_object_id, + self.profile_id, + TypeQuestionPoints.INFO_SLIDE, ) ), - "video_url": info_slide.video_url + "video_url": info_slide.video_url, } ) if serializer.is_valid(): - return Response(add_popup_data(serializer.data, self.request_task_object), status=status.HTTP_200_OK) + return Response( + add_popup_data(serializer.data, self.request_task_object), + status=status.HTTP_200_OK, + ) else: - return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST + ) class QuestionWriteAnswer(generics.RetrieveAPIView): @@ -221,12 +258,16 @@ def get(self, request, *args, **kwargs): text=question.text, description=question.description, files=[file.link for file in question.files.all()], - video_url=question.video_url + video_url=question.video_url, ) if user_answer := TaskObjUserResult.objects.get_answered( self.task_object_id, self.profile_id, TypeQuestionPoints.QUESTION_WRITE ): - write_question.answer = AnswerUserWriteData(id=user_answer.id, text=user_answer.text) + write_question.answer = AnswerUserWriteData( + id=user_answer.id, text=user_answer.text + ) data = asdict(write_question) - return Response(add_popup_data(data, self.request_task_object), status=status.HTTP_200_OK) + return Response( + add_popup_data(data, self.request_task_object), status=status.HTTP_200_OK + )