from dateutil.parser import parse from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from rest_framework.generics import get_object_or_404 from pitch_deck_generator.decks.models import ( PitchDeck, Question, QuestionAnswer, QuestionAnswerPhoto, QuestionDeckHint, ) class BasePitchDeckSerializer(serializers.ModelSerializer): class Meta: model = PitchDeck fields = ["id", "name", "description", "logo", "created", "updated"] extra_kwargs = { "id": {"read_only": True}, "logo": {"read_only": True}, "name": {"read_only": True}, "created": {"read_only": True}, "updated": {"read_only": True}, } class PitchDeckSerializer(serializers.ModelSerializer): class Meta: model = PitchDeck fields = "__all__" class HintSerializer(serializers.Serializer): type = serializers.ChoiceField(choices=Question.QuestionType.choices) value = serializers.JSONField() class QuestionSerializer(serializers.ModelSerializer): hint = serializers.SerializerMethodField(method_name="get_hint") next_id = serializers.SerializerMethodField(method_name="get_next_id") @extend_schema_field(HintSerializer) def get_hint(self, obj): if obj.hint: question_id = ( self.context["view"].kwargs["question_id"] if "question_id" in self.context["view"].kwargs else 1 ) if q := QuestionDeckHint.objects.filter( question_id=question_id, deck_id=self.context["view"].kwargs["deck_id"], ): return q.first().hint return {} return False @extend_schema_field(serializers.IntegerField) def get_next_id(self, obj): if q := Question.objects.filter(order=obj.order + 1): return q.first().id return 0 class Meta: model = Question fields = ["id", "text", "hint", "required", "type", "params", "next_id"] class AnswerSerializer(serializers.ModelSerializer): def __init__(self, *args, **kwargs): file_fields = kwargs.pop("file_fields", None) super().__init__(*args, **kwargs) if file_fields: field_update_dict = { field: serializers.FileField(required=False, write_only=True) for field in file_fields } self.fields.update(**field_update_dict) file = serializers.FileField(allow_null=True, required=False) class Meta: model = QuestionAnswer fields = ["answer", "deck", "question", "file"] extra_kwargs = { "deck": {"read_only": True}, "question": {"read_only": True}, } def validate(self, data): answer = data["answer"] question = get_object_or_404( Question, id=self.context["view"].kwargs["question_id"] ) deck = get_object_or_404(PitchDeck, id=self.context["view"].kwargs["deck_id"]) question_type = question.type params = question.params if question.params else {} match question_type: case "text": if type(answer) is not str: raise serializers.ValidationError("Incorrect type") if "max_length" in params: if len(answer) > params["max_length"]: raise serializers.ValidationError("Text is too long") case "number": if type(answer) is not str: raise serializers.ValidationError("Incorrect type") case "text_array": if type(answer) is not list: raise serializers.ValidationError("Incorrect type") if any([type(x) is not str for x in answer]): raise serializers.ValidationError("Incorrect type") case "range": slug = params["slug"] if slug not in answer: raise serializers.ValidationError("Value to found") if not isinstance(answer[slug], (int, float)): raise serializers.ValidationError("Incorrect type") if not (params["min_value"] <= answer[slug] <= params["max_value"]): raise serializers.ValidationError("Number is too big or too small") case "multiple_range": for slug in [x["slug"] for x in params["scrollbars"]]: if slug not in answer: raise serializers.ValidationError(f"Value {slug} to found") if not isinstance(answer[slug], (int, float)): raise serializers.ValidationError(f"Incorrect {slug} type") if not (params["min_value"] <= answer[slug] <= params["max_value"]): raise serializers.ValidationError( f"Number is too big or too small for {slug}" ) case "select": if answer not in params["options"]: raise serializers.ValidationError("No such option") case "date": try: parse(answer) except ValueError: raise serializers.ValidationError("Incorrect date type") case "photo": if answer: raise serializers.ValidationError("Answer should be blank") if not data["file"]: raise serializers.ValidationError("No file found") case "multiple_photo": if answer: raise serializers.ValidationError("Answer should be blank") for key, value in data.items(): if isinstance(value, InMemoryUploadedFile): if "_" not in key: raise serializers.ValidationError( "You should use file_num for file keys" ) try: int(key.split("_")[1]) except ValueError: raise serializers.ValidationError( "You should use file_num for file keys" ) case "photo_description": if not data["file"]: raise serializers.ValidationError("No file found") if type(answer) is not str: raise serializers.ValidationError("Incorrect type") if "max_length" in params: if len(answer) > params["max_length"]: raise serializers.ValidationError("Text is too long") case "multiple_photo_description": if type(answer) is not list: raise serializers.ValidationError("Incorrect type") if any([type(x) is not str for x in answer]): raise serializers.ValidationError("Incorrect type") len_f = 0 for key, value in data.items(): if isinstance(value, TemporaryUploadedFile): if "_" not in key: raise serializers.ValidationError( "You should use file_num for file keys" ) try: int(key.split("_")[1]) except ValueError: raise serializers.ValidationError( "You should use file_num for file keys" ) len_f += 1 if len_f != len(answer): raise serializers.ValidationError( "You should provide the same amount of answers in list as photos" ) case "multiple_link_description": if type(answer) is not dict: raise serializers.ValidationError("Incorrect type") if any([type(x) is not str for x in answer.keys()]): raise serializers.ValidationError("Incorrect type") if any([type(x) is not str for x in answer.values()]): raise serializers.ValidationError("Incorrect type") data["question_id"] = question.id data["deck_id"] = deck.id return data def create(self, validated_data): q = QuestionAnswer.objects.get_or_create( deck_id=validated_data["deck_id"], question_id=validated_data["question_id"] )[0] q.answer = validated_data["answer"] q.save() s = [ key for key, val in validated_data.items() if isinstance(val, TemporaryUploadedFile) and key != "file" ] if "file" in validated_data: QuestionAnswerPhoto.objects.create(answer=q, file=validated_data["file"]) elif s: s.sort(key=lambda x: int(x.split("_")[1])) for key in s: QuestionAnswerPhoto.objects.create(answer=q, file=validated_data[key]) return q