text: from collections import defaultdict
  from typing import Optional
  
  from django.db.models import ObjectDoesNotExist
  from drf_spectacular.utils import extend_schema_field
  from rest_framework import serializers
  from rest_framework.exceptions import NotFound, ValidationError
  from rest_framework.fields import RegexField
  from rest_framework.serializers import ModelSerializer
  
  from core.utils.constants import YOUTUBE_VIDEO_ID_REGEX
  from tournesol.models import Entity, EntityCriteriaScore
  from tournesol.utils.api_youtube import VideoNotFound
  
  
  class VideoSerializer(ModelSerializer):
      duration = serializers.SerializerMethodField()
  
      class Meta:
          model = Entity
          fields = [
              "video_id",
              "name",
              "description",
              "publication_date",
              "views",
              "uploader",
              "language",
              "rating_n_ratings",
              "rating_n_contributors",
              "duration",
          ]
          read_only_fields = [
              "name",
              "description",
              "publication_date",
              "views",
              "uploader",
              "language",
              "rating_n_ratings",
              "rating_n_contributors",
              "duration",
          ]
  
      def create(self, validated_data):
          try:
              return Entity.create_from_video_id(validated_data["video_id"])
          except VideoNotFound:
              raise NotFound("The video has not been found. `video_id` may be incorrect.")
  
      # Convert duration to seconds to facilitate use of Humanize package
      def get_duration(self, obj) -> Optional[int]:
          if obj.duration:
              return int(obj.duration.total_seconds())
          return None
  
  
  class RelatedVideoSerializer(VideoSerializer):
      """
      A video serializer that will create the Entity object on validation
      if it does not exist in the database yet.
  
      Used by ModelSerializer(s) having one or more nested relations with Entity,
      and having the constraint to ensure that video instances exist before
      they can be saved properly.
      """
  
      video_id = RegexField(YOUTUBE_VIDEO_ID_REGEX)
  
      def validate_video_id(self, value):
          try:
              Entity.objects.get(video_id=value)
          except ObjectDoesNotExist:
              try:
                  Entity.create_from_video_id(value)
              except VideoNotFound:
                  raise ValidationError(
                      "The video has not been found. `video_id` may be incorrect."
                  )
          return value
  
  
  class EntityCriteriaScoreSerializer(ModelSerializer):
      class Meta:
          model = EntityCriteriaScore
          fields = ["criteria", "score"]
  
  
  class VideoSerializerWithCriteria(VideoSerializer):
      criteria_scores = EntityCriteriaScoreSerializer(many=True)
  
      class Meta(VideoSerializer.Meta):
          fields = VideoSerializer.Meta.fields + ["criteria_scores"]
          # Overriding 'read_only_fields' should not be necessary here. However, due to a
          # limitation in DRF, some extra attributes from model fields (nullable, etc.) are
          # not present in the schema generated by drf-spectacular for read_only fields.
          # See https://github.com/tfranzel/drf-spectacular/issues/383 for more details.
          # This serializer is always used as read-only, so the 'read_only_fields' definition
          # can be discarded safely to generate a correct OpenAPI schema.
          read_only_fields = []
  
  
  class EntityPollSerializer(serializers.Serializer):
      name = serializers.CharField()
      criteria_scores = EntityCriteriaScoreSerializer(many=True)
  
  
  class EntitySerializer(ModelSerializer):
      polls = serializers.SerializerMethodField()
  
      class Meta:
          model = Entity
          fields = ["uid", "type", "metadata", "polls"]
  
      @extend_schema_field(EntityPollSerializer(many=True))
      def get_polls(self, obj):
          poll_to_scores = defaultdict(list)
          for score in obj.criteria_scores.all():
              poll_to_scores[score.poll.name].append(score)
          items = [
              {"name": name, "criteria_scores": scores}
              for (name, scores) in poll_to_scores.items()
          ]
          return EntityPollSerializer(items, many=True).data
  