Please note that this is a squshed commit and the work of: Symbolist, macdiesel, nedbat, doctoryes, muzaffaryousaf and muhammad-ammar
226 lines
7.8 KiB
Python
226 lines
7.8 KiB
Python
"""Defines serializers used by the Team API."""
|
|
from copy import deepcopy
|
|
from django.contrib.auth.models import User
|
|
from django.db.models import Count
|
|
from django.conf import settings
|
|
|
|
from django_countries import countries
|
|
from rest_framework import serializers
|
|
|
|
from openedx.core.lib.api.serializers import CollapsedReferenceSerializer
|
|
from openedx.core.lib.api.fields import ExpandableField
|
|
from openedx.core.djangoapps.user_api.accounts.serializers import UserReadOnlySerializer
|
|
|
|
from lms.djangoapps.teams.models import CourseTeam, CourseTeamMembership
|
|
|
|
|
|
class CountryField(serializers.Field):
|
|
"""
|
|
Field to serialize a country code.
|
|
"""
|
|
|
|
COUNTRY_CODES = dict(countries).keys()
|
|
|
|
def to_representation(self, obj):
|
|
"""
|
|
Represent the country as a 2-character unicode identifier.
|
|
"""
|
|
return unicode(obj)
|
|
|
|
def to_internal_value(self, data):
|
|
"""
|
|
Check that the code is a valid country code.
|
|
|
|
We leave the data in its original format so that the Django model's
|
|
CountryField can convert it to the internal representation used
|
|
by the django-countries library.
|
|
"""
|
|
if data and data not in self.COUNTRY_CODES:
|
|
raise serializers.ValidationError(
|
|
u"{code} is not a valid country code".format(code=data)
|
|
)
|
|
return data
|
|
|
|
|
|
class UserMembershipSerializer(serializers.ModelSerializer):
|
|
"""Serializes CourseTeamMemberships with only user and date_joined
|
|
|
|
Used for listing team members.
|
|
"""
|
|
profile_configuration = deepcopy(settings.ACCOUNT_VISIBILITY_CONFIGURATION)
|
|
profile_configuration['shareable_fields'].append('url')
|
|
profile_configuration['public_fields'].append('url')
|
|
|
|
user = ExpandableField(
|
|
collapsed_serializer=CollapsedReferenceSerializer(
|
|
model_class=User,
|
|
id_source='username',
|
|
view_name='accounts_api',
|
|
read_only=True,
|
|
),
|
|
expanded_serializer=UserReadOnlySerializer(configuration=profile_configuration),
|
|
)
|
|
|
|
class Meta(object):
|
|
model = CourseTeamMembership
|
|
fields = ("user", "date_joined", "last_activity_at")
|
|
read_only_fields = ("date_joined", "last_activity_at")
|
|
|
|
|
|
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)
|
|
country = CountryField()
|
|
|
|
class Meta(object):
|
|
model = CourseTeam
|
|
fields = (
|
|
"id",
|
|
"discussion_topic_id",
|
|
"name",
|
|
"course_id",
|
|
"topic_id",
|
|
"date_created",
|
|
"description",
|
|
"country",
|
|
"language",
|
|
"last_activity_at",
|
|
"membership",
|
|
)
|
|
read_only_fields = ("course_id", "date_created", "discussion_topic_id", "last_activity_at")
|
|
|
|
|
|
class CourseTeamCreationSerializer(serializers.ModelSerializer):
|
|
"""Deserializes a CourseTeam for creation."""
|
|
|
|
country = CountryField(required=False)
|
|
|
|
class Meta(object):
|
|
model = CourseTeam
|
|
fields = (
|
|
"name",
|
|
"course_id",
|
|
"description",
|
|
"topic_id",
|
|
"country",
|
|
"language",
|
|
)
|
|
|
|
def create(self, validated_data):
|
|
team = CourseTeam.create(
|
|
name=validated_data.get("name", ''),
|
|
course_id=validated_data.get("course_id"),
|
|
description=validated_data.get("description", ''),
|
|
topic_id=validated_data.get("topic_id", ''),
|
|
country=validated_data.get("country", ''),
|
|
language=validated_data.get("language", ''),
|
|
)
|
|
team.save()
|
|
return team
|
|
|
|
|
|
class CourseTeamSerializerWithoutMembership(CourseTeamSerializer):
|
|
"""The same as the `CourseTeamSerializer`, but elides the membership field.
|
|
|
|
Intended to be used as a sub-serializer for serializing team
|
|
memberships, since the membership field is redundant in that case.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(CourseTeamSerializerWithoutMembership, self).__init__(*args, **kwargs)
|
|
del self.fields['membership']
|
|
|
|
|
|
class MembershipSerializer(serializers.ModelSerializer):
|
|
"""Serializes CourseTeamMemberships with information about both teams and users."""
|
|
profile_configuration = deepcopy(settings.ACCOUNT_VISIBILITY_CONFIGURATION)
|
|
profile_configuration['shareable_fields'].append('url')
|
|
profile_configuration['public_fields'].append('url')
|
|
|
|
user = ExpandableField(
|
|
collapsed_serializer=CollapsedReferenceSerializer(
|
|
model_class=User,
|
|
id_source='username',
|
|
view_name='accounts_api',
|
|
read_only=True,
|
|
),
|
|
expanded_serializer=UserReadOnlySerializer(configuration=profile_configuration)
|
|
)
|
|
team = ExpandableField(
|
|
collapsed_serializer=CollapsedReferenceSerializer(
|
|
model_class=CourseTeam,
|
|
id_source='team_id',
|
|
view_name='teams_detail',
|
|
read_only=True,
|
|
),
|
|
expanded_serializer=CourseTeamSerializerWithoutMembership(read_only=True),
|
|
)
|
|
|
|
class Meta(object):
|
|
model = CourseTeamMembership
|
|
fields = ("user", "team", "date_joined", "last_activity_at")
|
|
read_only_fields = ("date_joined", "last_activity_at")
|
|
|
|
|
|
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, checking if team_count
|
|
is already present in the topic data, and if not, querying the CourseTeam
|
|
model to get the count. Requires that `context` is provided with a valid course_id
|
|
in order to filter teams within the course.
|
|
"""
|
|
team_count = serializers.SerializerMethodField()
|
|
|
|
def get_team_count(self, topic):
|
|
"""Get the number of teams associated with this topic"""
|
|
# If team_count is already present (possible if topic data was pre-processed for sorting), return it.
|
|
if 'team_count' in topic:
|
|
return topic['team_count']
|
|
else:
|
|
return CourseTeam.objects.filter(course_id=self.context['course_id'], topic_id=topic['id']).count()
|
|
|
|
|
|
class BulkTeamCountTopicListSerializer(serializers.ListSerializer): # pylint: disable=abstract-method
|
|
"""
|
|
List serializer for efficiently serializing a set of topics.
|
|
"""
|
|
|
|
def to_representation(self, obj):
|
|
"""Adds team_count to each topic. """
|
|
data = super(BulkTeamCountTopicListSerializer, self).to_representation(obj)
|
|
add_team_count(data, self.context["course_id"])
|
|
return data
|
|
|
|
|
|
class BulkTeamCountTopicSerializer(BaseTopicSerializer): # pylint: disable=abstract-method
|
|
"""
|
|
Serializes a set of topics, adding the team_count field to each topic as a bulk operation.
|
|
Requires that `context` is provided with a valid course_id in order to filter teams within the course.
|
|
"""
|
|
class Meta(object):
|
|
list_serializer_class = BulkTeamCountTopicListSerializer
|
|
|
|
|
|
def add_team_count(topics, course_id):
|
|
"""
|
|
Helper method to add team_count for a list of topics.
|
|
This allows for a more efficient single query.
|
|
"""
|
|
topic_ids = [topic['id'] for topic in topics]
|
|
teams_per_topic = CourseTeam.objects.filter(
|
|
course_id=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 topics:
|
|
topic['team_count'] = topics_to_team_count.get(topic['id'], 0)
|