Source code for intranet.apps.eighth.serializers

# pylint: disable=abstract-method
import collections
import functools
import logging

from django.contrib.auth import get_user_model
from django.db import transaction
from django.db.models import Count
from rest_framework import serializers
from rest_framework.reverse import reverse

from .models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor

logger = logging.getLogger(__name__)


[docs]class EighthActivityListSerializer(serializers.HyperlinkedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name="api_eighth_activity_detail")
[docs] class Meta: model = EighthActivity fields = ("id", "url", "name")
[docs]class EighthActivityDetailSerializer(serializers.HyperlinkedModelSerializer): scheduled_on = serializers.SerializerMethodField("fetch_scheduled_on")
[docs] def fetch_scheduled_on(self, act): scheduled_on = collections.OrderedDict() scheduled_activities = EighthScheduledActivity.objects.filter(activity=act).select_related("block").order_by("block__date") # user = self.context.get("user", self.context["request"].user) # favorited_activities = set(user.favorited_activity_set # .values_list("id", flat=True)) # available_restricted_acts = EighthActivity.restricted_activities_available_to_user(user) for scheduled_activity in scheduled_activities: scheduled_on[scheduled_activity.block.id] = { "id": scheduled_activity.block.id, "date": scheduled_activity.block.date, "block_letter": scheduled_activity.block.block_letter, "url": reverse("api_eighth_block_detail", args=[scheduled_activity.block.id], request=self.context["request"]), "roster": { "id": scheduled_activity.id, "url": reverse("api_eighth_scheduled_activity_signup_list", args=[scheduled_activity.id], request=self.context["request"]), }, } return scheduled_on
[docs] class Meta: model = EighthActivity fields = ( "id", "name", "description", "administrative", "restricted", "presign", "one_a_day", "both_blocks", "sticky", "special", "scheduled_on", )
[docs]class EighthBlockListSerializer(serializers.HyperlinkedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name="api_eighth_block_detail")
[docs] class Meta: model = EighthBlock fields = ("id", "url", "date", "block_letter", "locked")
[docs]class FallbackDict(dict): def __init__(self, fallback): super().__init__() self.fallback = fallback def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError: return self.fallback(key)
[docs]class EighthBlockDetailSerializer(serializers.Serializer): id = serializers.ReadOnlyField() activities = serializers.SerializerMethodField("fetch_activity_list_with_metadata") date = serializers.DateField() block_letter = serializers.CharField(max_length=10) comments = serializers.CharField(max_length=100)
[docs] def process_scheduled_activity( self, scheduled_activity, request=None, user=None, favorited_activities=None, subscribed_activities=None, recommended_activities=None, available_restricted_acts=None, ): activity = scheduled_activity.activity if user: is_non_student_admin = user.is_eighth_admin and not user.is_student else: is_non_student_admin = False restricted_for_user = scheduled_activity.get_restricted() and not is_non_student_admin and (activity.id not in available_restricted_acts) prefix = "Special: " if activity.special else "" prefix += activity.name if scheduled_activity.title: prefix += " - " + scheduled_activity.title middle = " (R)" if restricted_for_user else "" if user is not None and scheduled_activity.is_user_stickied(user): suffix = " (S)" else: suffix = "" suffix += " (BB)" if scheduled_activity.is_both_blocks() else "" suffix += " (A)" if activity.administrative else "" suffix += " (Deleted)" if activity.deleted else "" name_with_flags = prefix + middle + suffix name_with_flags_for_user = prefix + (middle if restricted_for_user else "") + suffix activity_info = { "id": activity.id, "aid": activity.aid, "scheduled_activity": { "id": scheduled_activity.id, "url": reverse("api_eighth_scheduled_activity_signup_list", args=[scheduled_activity.id], request=request), }, "url": reverse("api_eighth_activity_detail", args=[activity.id], request=request), "name": activity.name, "name_with_flags": name_with_flags, "name_with_flags_for_user": name_with_flags_for_user, "description": activity.description, "cancelled": scheduled_activity.cancelled, "favorited": activity.id in favorited_activities, "subscribed_to": activity.id in subscribed_activities, "subscriptions_enabled": activity.subscriptions_enabled, "roster": { "count": 0, "capacity": 0, "url": reverse("api_eighth_scheduled_activity_signup_list", args=[scheduled_activity.id], request=request), }, "rooms": [], "sponsors": [], "restricted": scheduled_activity.get_restricted(), "restricted_for_user": restricted_for_user, "both_blocks": scheduled_activity.is_both_blocks(), "one_a_day": activity.one_a_day, "special": scheduled_activity.get_special(), "administrative": scheduled_activity.get_administrative(), "presign": activity.presign, "presign_time": scheduled_activity.is_too_early_to_signup()[1].strftime("%A, %B %-d at %-I:%M %p"), "sticky": scheduled_activity.is_activity_sticky(), "user_sticky": scheduled_activity.is_user_stickied(user), "finance": "", # TODO: refactor JS to remove this "title": scheduled_activity.title, "comments": scheduled_activity.comments, # TODO: refactor JS to remove this "display_text": "", # TODO: Remove together with other eighth waitlist functionality "waitlist_count": 0, } if user: # TODO: Remove together with other eighth waitlist functionality activity_info["waitlisted"] = False activity_info["waitlist_position"] = 0 activity_info["is_recommended"] = activity in recommended_activities return activity_info
[docs] def get_activity( self, user, favorited_activities, subscribed_activities, recommended_activities, available_restricted_acts, block_id, activity_id, scheduled_activity=None, ): if scheduled_activity is None: scheduled_activity = EighthScheduledActivity.objects.get(block_id=block_id, activity_id=activity_id) return self.process_scheduled_activity( scheduled_activity, self.context["request"], user, favorited_activities, subscribed_activities, recommended_activities, available_restricted_acts, )
[docs] def get_scheduled_activity(self, scheduled_activity_id): return EighthScheduledActivity.objects.get(id=scheduled_activity_id).activity_id
[docs] @transaction.atomic def fetch_activity_list_with_metadata(self, block): user = self.context.get("user", self.context["request"].user) if user: favorited_activities = set(user.favorited_activity_set.values_list("id", flat=True)) recommended_activities = user.recommended_activities subscribed_activities = set(user.subscribed_activity_set.values_list("id", flat=True)) else: favorited_activities = set() recommended_activities = set() subscribed_activities = set() available_restricted_acts = EighthActivity.restricted_activities_available_to_user(user) activity_list = FallbackDict( functools.partial(self.get_activity, user, favorited_activities, recommended_activities, available_restricted_acts, block.id) ) scheduled_activity_to_activity_map = FallbackDict(self.get_scheduled_activity) # Find all scheduled activities that don't correspond to deleted activities. # Also move administrative activities to the end of the list. It appears that it is not possible to sort on "activity__administrative OR # administrative", so we just sort by each in turn. The exact order of administrative activities does not matter *too* much. scheduled_activities = ( block.eighthscheduledactivity_set.exclude(activity__deleted=True) .select_related("activity") .order_by("activity__administrative", "administrative", "activity__name") ) for scheduled_activity in scheduled_activities: # Avoid re-fetching scheduled_activity. activity_info = self.get_activity( user, favorited_activities, subscribed_activities, recommended_activities, available_restricted_acts, None, None, scheduled_activity ) activity = scheduled_activity.activity scheduled_activity_to_activity_map[scheduled_activity.id] = activity.id activity_list[activity.id] = activity_info # Find the number of students signed up for every activity # in this block activities_with_signups = ( EighthSignup.objects.filter(scheduled_activity__block=block) .exclude(scheduled_activity__activity__deleted=True) .values_list("scheduled_activity__activity_id") .annotate(user_count=Count("scheduled_activity")) ) for activity_id, user_count in activities_with_signups: activity_list[activity_id]["roster"]["count"] = user_count sponsors_dict = EighthSponsor.objects.values_list("id", "user_id", "first_name", "last_name", "show_full_name") all_sponsors = { sponsor[0]: {"user_id": sponsor[1], "name": sponsor[2] + " " + sponsor[3] if sponsor[4] else sponsor[3]} for sponsor in sponsors_dict } activity_ids = scheduled_activities.values_list("activity__id") sponsorships = ( EighthActivity.sponsors.through.objects.filter(eighthactivity_id__in=activity_ids) .select_related("sponsors") .values_list("eighthactivity", "eighthsponsor") ) scheduled_activity_ids = scheduled_activities.values_list("id", flat=True) overridden_sponsorships = EighthScheduledActivity.sponsors.through.objects.filter( eighthscheduledactivity_id__in=scheduled_activity_ids ).values_list("eighthscheduledactivity", "eighthsponsor") for activity_id, sponsor_id in sponsorships: sponsor = all_sponsors[sponsor_id] if sponsor["name"]: name = sponsor["name"] elif sponsor["user_id"]: try: name = get_user_model().objects.get(id=sponsor["user_id"]).last_name except get_user_model().DoesNotExist: name = None else: name = None if activity_id in activity_list: activity_list[activity_id]["sponsors"].append(name) activities_sponsors_overridden = set() for scheduled_activity_id, sponsor_id in overridden_sponsorships: activity_id = scheduled_activity_to_activity_map[scheduled_activity_id] sponsor = all_sponsors[sponsor_id] if activity_id not in activities_sponsors_overridden: activities_sponsors_overridden.add(activity_id) activity_list[activity_id]["sponsors"].clear() if sponsor["name"]: name = sponsor["name"] elif sponsor["user_id"]: try: name = get_user_model().objects.get(id=sponsor["user_id"]).last_name except get_user_model().DoesNotExist: name = None else: name = None if activity_id in activity_list: activity_list[activity_id]["sponsors"].append(name) roomings = EighthActivity.rooms.through.objects.filter(eighthactivity_id__in=activity_ids).select_related("eighthroom", "eighthactivity") overridden_roomings = EighthScheduledActivity.rooms.through.objects.filter( eighthscheduledactivity_id__in=scheduled_activity_ids ).select_related("eighthroom") for rooming in roomings: activity_id = rooming.eighthactivity.id activity_cap = rooming.eighthactivity.default_capacity room_name = rooming.eighthroom.name activity_list[activity_id]["rooms"].append(room_name) if activity_cap: # use activity default capacity instead of sum of activity rooms activity_list[activity_id]["roster"]["capacity"] = activity_cap else: activity_list[activity_id]["roster"]["capacity"] += rooming.eighthroom.capacity activities_rooms_overridden = [] for rooming in overridden_roomings: scheduled_activity_id = rooming.eighthscheduledactivity_id activity_id = scheduled_activity_to_activity_map[scheduled_activity_id] if activity_id not in activities_rooms_overridden: activities_rooms_overridden.append(activity_id) del activity_list[activity_id]["rooms"][:] activity_list[activity_id]["roster"]["capacity"] = 0 room_name = rooming.eighthroom.name activity_list[activity_id]["rooms"].append(room_name) activity_list[activity_id]["roster"]["capacity"] += rooming.eighthroom.capacity for scheduled_activity in scheduled_activities: if scheduled_activity.capacity is not None: capacity = scheduled_activity.capacity act_id = scheduled_activity.activity.id activity_list[act_id]["roster"]["capacity"] = capacity return activity_list
[docs] class Meta: fields = ("id", "activities", "date", "block_letter")
[docs]class EighthSignupSerializer(serializers.ModelSerializer): block = serializers.SerializerMethodField("block_info") activity = serializers.SerializerMethodField("activity_info") scheduled_activity = serializers.SerializerMethodField("scheduled_activity_info")
[docs] def block_info(self, signup): return { "id": signup.scheduled_activity.block.id, "date": signup.scheduled_activity.block.date, "url": reverse("api_eighth_block_detail", args=[signup.scheduled_activity.block.id], request=self.context["request"]), "block_letter": signup.scheduled_activity.block.block_letter, }
[docs] def activity_info(self, signup): return { "id": signup.scheduled_activity.activity.id, "title": signup.scheduled_activity.title_with_flags, "url": reverse("api_eighth_activity_detail", args=[signup.scheduled_activity.activity.id], request=self.context["request"]), }
[docs] def scheduled_activity_info(self, signup): return signup.scheduled_activity.id
[docs] class Meta: model = EighthSignup fields = ("id", "block", "activity", "scheduled_activity", "user")
[docs]class UserSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField()
[docs] def get_url(self, user): return reverse("api_user_profile_detail", args=[user.id], request=self.context["request"])
[docs] class Meta: model = get_user_model() fields = ("id", "full_name", "username", "url")
[docs]class EighthScheduledActivitySerializer(serializers.ModelSerializer): block = serializers.SerializerMethodField("block_info") activity = serializers.SerializerMethodField("activity_info") signups = serializers.SerializerMethodField("signups_info") name = serializers.SerializerMethodField() capacity = serializers.SerializerMethodField() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if "context" in kwargs and "request" in kwargs["context"]: self.request = kwargs["context"]["request"] else: self.request = None
[docs] def get_name(self, scheduled_activity): return scheduled_activity.title_with_flags
[docs] def get_capacity(self, scheduled_activity): return scheduled_activity.get_true_capacity()
[docs] def block_info(self, scheduled_activity): return { "id": scheduled_activity.block.id, "date": scheduled_activity.block.date, "url": reverse("api_eighth_block_detail", args=[scheduled_activity.block.id], request=self.context["request"]), }
[docs] def activity_info(self, scheduled_activity): return { "id": scheduled_activity.activity.id, "title": scheduled_activity.title_with_flags, "url": reverse("api_eighth_activity_detail", args=[scheduled_activity.activity.id], request=self.context["request"]), }
[docs] def signups_info(self, scheduled_activity): signups = scheduled_activity.members if self.request: signups = scheduled_activity.get_viewable_members_serializer(self.request) serializer = UserSerializer(signups, context=self.context, many=True) return {"members": serializer.data, "count": scheduled_activity.members.count(), "viewable_count": signups.count()}
[docs] def num_signups(self, scheduled_activity): return scheduled_activity.members.count()
[docs] class Meta: model = EighthScheduledActivity fields = ("id", "name", "block", "activity", "signups", "capacity")
[docs]def add_signup_validator(value): if "scheduled_activity" in value: return if "block" in value and "activity" in value and not value.get("use_scheduled_activity", False): return raise serializers.ValidationError( "Either scheduled_activity, or block and activity must exist. use_scheduled_activity must be false to use block and activity." )
[docs]class EighthAddSignupSerializer(serializers.Serializer): block = serializers.PrimaryKeyRelatedField(queryset=EighthBlock.objects.all(), required=False) activity = serializers.PrimaryKeyRelatedField(queryset=EighthActivity.objects.all(), required=False) scheduled_activity = serializers.PrimaryKeyRelatedField( queryset=EighthScheduledActivity.objects.select_related("activity").select_related("block"), required=False ) use_scheduled_activity = serializers.BooleanField(required=False) force = serializers.BooleanField(required=False)
[docs] class Meta: validators = [add_signup_validator]
[docs]class EighthToggleFavoriteSerializer(serializers.Serializer): activity = serializers.PrimaryKeyRelatedField(queryset=EighthActivity.objects.all(), required=False)