"""Defines serializers used by the Team API.""" from django.contrib.auth.models import User from django.db.models import Count from rest_framework import serializers from openedx.core.lib.api.serializers import CollapsedReferenceSerializer, PaginationSerializer from openedx.core.lib.api.fields import ExpandableField from openedx.core.djangoapps.user_api.serializers import UserSerializer from .models import CourseTeam, CourseTeamMembership class UserMembershipSerializer(serializers.ModelSerializer): """Serializes CourseTeamMemberships with only user and date_joined Used for listing team members. """ user = ExpandableField( collapsed_serializer=CollapsedReferenceSerializer( model_class=User, id_source='username', view_name='accounts_api', read_only=True, ), expanded_serializer=UserSerializer(), ) class Meta(object): """Defines meta information for the ModelSerializer.""" model = CourseTeamMembership fields = ("user", "date_joined") read_only_fields = ("date_joined",) class CourseTeamSerializer(serializers.ModelSerializer): """Serializes a CourseTeam with membership information.""" id = serializers.CharField(source='team_id', read_only=True) # pylint: disable=invalid-name membership = UserMembershipSerializer(many=True, read_only=True) class Meta(object): """Defines meta information for the ModelSerializer.""" model = CourseTeam fields = ( "id", "name", "is_active", "course_id", "topic_id", "date_created", "description", "country", "language", "membership", ) read_only_fields = ("course_id", "date_created") class CourseTeamCreationSerializer(serializers.ModelSerializer): """Deserializes a CourseTeam for creation.""" class Meta(object): """Defines meta information for the ModelSerializer.""" model = CourseTeam fields = ( "name", "course_id", "description", "topic_id", "country", "language", ) def restore_object(self, attrs, instance=None): """Restores a CourseTeam instance from the given attrs.""" return CourseTeam.create( name=attrs.get("name", ''), course_id=attrs.get("course_id"), description=attrs.get("description", ''), topic_id=attrs.get("topic_id", ''), country=attrs.get("country", ''), language=attrs.get("language", ''), ) class MembershipSerializer(serializers.ModelSerializer): """Serializes CourseTeamMemberships with information about both teams and users.""" user = ExpandableField( collapsed_serializer=CollapsedReferenceSerializer( model_class=User, id_source='username', view_name='accounts_api', read_only=True, ), expanded_serializer=UserSerializer(read_only=True) ) team = ExpandableField( collapsed_serializer=CollapsedReferenceSerializer( model_class=CourseTeam, id_source='team_id', view_name='teams_detail', read_only=True, ), expanded_serializer=CourseTeamSerializer(read_only=True) ) class Meta(object): """Defines meta information for the ModelSerializer.""" model = CourseTeamMembership fields = ("user", "team", "date_joined") read_only_fields = ("date_joined",) class BaseTopicSerializer(serializers.Serializer): """Serializes a topic without team_count.""" description = serializers.CharField() name = serializers.CharField() id = serializers.CharField() # pylint: disable=invalid-name class TopicSerializer(BaseTopicSerializer): """ Adds team_count to the basic topic serializer. Use only when serializing a single topic. When serializing many topics, use `PaginatedTopicSerializer` to avoid O(N) SQL queries. Requires that `context` is provided with a valid course_id in order to filter teams within the course. """ team_count = serializers.SerializerMethodField('get_team_count') def get_team_count(self, topic): """Get the number of teams associated with this topic""" return CourseTeam.objects.filter(course_id=self.context['course_id'], topic_id=topic['id']).count() class PaginatedTopicSerializer(PaginationSerializer): """ Serializes a set of topics, adding team_count field to each topic. Requires that `context` is provided with a valid course_id in order to filter teams within the course. """ class Meta(object): """Defines meta information for the PaginatedTopicSerializer.""" object_serializer_class = BaseTopicSerializer def __init__(self, *args, **kwargs): """Adds team_count to each topic.""" super(PaginatedTopicSerializer, self).__init__(*args, **kwargs) # The following query gets all the team_counts for each topic # and outputs the result as a list of dicts (one per topic). topic_ids = [topic['id'] for topic in self.data['results']] teams_per_topic = CourseTeam.objects.filter( course_id=self.context['course_id'], topic_id__in=topic_ids ).values('topic_id').annotate(team_count=Count('topic_id')) topics_to_team_count = {d['topic_id']: d['team_count'] for d in teams_per_topic} for topic in self.data['results']: topic['team_count'] = topics_to_team_count.get(topic['id'], 0)