Newer
Older
#
# steering, Programme/schedule management for AURA
#
# Copyright (C) 2011-2017, 2020, Ernesto Rico Schmidt
# Copyright (C) 2017-2019, Ingo Leindecker
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from profile.models import Profile
from profile.serializers import ProfileSerializer
from typing import List
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from program.models import (
Category,
FundingCategory,
Host,
SOLUTION_CHOICES = {
"theirs": "Discard projected timeslot. Keep existing timeslot(s).",
"ours": "Create projected timeslot. Delete existing timeslot(s).",
"theirs-start": (
"Keep existing timeslot. Create projected timeslot with start time of existing end."
),
"ours-start": (
"Create projected timeslot. Change end of existing timeslot to projected start time."
),
"theirs-end": (
"Keep existing timeslot. Create projected timeslot with end of existing start time."
),
"ours-end": (
"Create projected timeslot. Change start of existing timeslot to projected end time."
),
"theirs-both": (
"Keep existing timeslot. "
"Create two projected timeslots with end of existing start and start of existing end."
),
"ours-both": (
"Create projected timeslot. Split existing timeslot into two: \n\n"
"* set existing end time to projected start,\n"
"* create another timeslot with start = projected end and end = existing end."
),
}
class ErrorSerializer(serializers.Serializer):
message = serializers.CharField()
code = serializers.CharField(allow_null=True)
class UserSerializer(serializers.ModelSerializer):
# Add profile fields to JSON
profile = ProfileSerializer(required=False)
class Meta:
model = User
fields = (
"id",
"username",
"first_name",
"last_name",
"email",
"is_staff",
"is_active",
"is_superuser",
"password",
"profile",
)
def create(self, validated_data):
"""
Create and return a new User instance, given the validated data.
"""
profile_data = (
validated_data.pop("profile") if "profile" in validated_data else None
)
user = super(UserSerializer, self).create(validated_data)
user.date_joined = timezone.now()
user.set_password(validated_data["password"])
if profile_data:
profile = Profile(
user=user,
cba_username=profile_data.get("cba_username").strip(),
cba_user_token=profile_data.get("cba_user_token").strip(),
created_by=self.context["user"],
profile.save()
def update(self, instance, validated_data):
"""
Update and return an existing User instance, given the validated data.
"""
instance.first_name = validated_data.get("first_name", instance.first_name)
instance.last_name = validated_data.get("last_name", instance.last_name)
instance.email = validated_data.get("email", instance.email)
instance.is_active = validated_data.get("is_active", instance.is_active)
instance.is_staff = validated_data.get("is_staff", instance.is_staff)
instance.is_superuser = validated_data.get(
"is_superuser", instance.is_superuser
)
profile_data = (
validated_data.pop("profile") if "profile" in validated_data else None
)
if profile_data:
# TODO: How to hook into this from ProfileSerializer without having to call it here?
try:
profile = Profile.objects.get(user=instance.id)
except ObjectDoesNotExist:
profile = Profile.objects.create(user=instance, **profile_data)
profile.cba_username = profile_data.get("cba_username")
profile.cba_user_token = profile_data.get("cba_user_token")
profile.updated_by = self.context["user"]
profile.save()
instance.save()
return instance
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ("id", "name", "subtitle", "slug", "is_active", "description")
class LinkSerializer(serializers.ModelSerializer):
class Meta:
class HostLinkSerializer(serializers.ModelSerializer):
class Meta:
model = HostLink
fields = ("description", "url")
class HostSerializer(serializers.ModelSerializer):
links = HostLinkSerializer(many=True, required=False)
thumbnails = serializers.SerializerMethodField()
def get_thumbnails(host) -> List[str]:
"""Returns thumbnails"""
thumbnails = []
if host.image.name and THUMBNAIL_SIZES:
for size in THUMBNAIL_SIZES:
thumbnails.append(host.image.crop[size].name)
return thumbnails
class Meta:
model = Host
fields = (
"name",
"email",
"biography",
"is_active",
"links",
"thumbnails",
# ModelWithImageFields
"image",
"image_ppoi",
"image_width",
"image_height",
"image_credits",
"image_alt_text",
# ModelWithCreatedUpdatedFields
"created_at",
"created_by",
"updated_at",
"updated_by",
read_only_fields = (
"created_at",
"created_by",
"updated_at",
"updated_by",
)
def create(self, validated_data):
links_data = validated_data.pop("links", [])
host = Host.objects.create(**validated_data | self.context)
for link_data in links_data:
HostLink.objects.create(host=host, **link_data)
host.save()
return host
def update(self, instance, validated_data):
"""
Update and return an existing Host instance, given the validated data.
"""
instance.name = validated_data.get("name", instance.name)
instance.is_active = validated_data.get("is_active", instance.is_active)
instance.email = validated_data.get("email", instance.email)
instance.biography = validated_data.get("biography", instance.biography)
instance.image = validated_data.get("image", instance.image)
instance.image_ppoi = validated_data.get("image_ppoi", instance.image_ppoi)
instance.image_credits = validated_data.get(
"image_credits", instance.image_credits
)
instance.image_alt_text = validated_data.get(
"image_alt_text", instance.image_alt_text
)
instance.updated_by = self.context.get("updated_by")
if instance.links.count() > 0:
for link in instance.links.all():
link.delete(keep_parents=True)
if links_data := validated_data.get("links"):
for link_data in links_data:
HostLink.objects.create(host=instance, **link_data)
instance.save()
return instance
class LanguageSerializer(serializers.ModelSerializer):
class Meta:
model = Language
fields = ("id", "name", "is_active")
class SerializerWithIdNameSlugIsActive(serializers.ModelSerializer):
class Meta:
fields = ("id", "name", "slug", "is_active")
class TopicSerializer(SerializerWithIdNameSlugIsActive):
class Meta:
model = Topic
class MusicFocusSerializer(SerializerWithIdNameSlugIsActive):
class Meta:
model = MusicFocus
class TypeSerializer(SerializerWithIdNameSlugIsActive):

Ingo Leindecker
committed
class Meta:
model = Type
class FundingCategorySerializer(SerializerWithIdNameSlugIsActive):

Ingo Leindecker
committed
class Meta:

Ingo Leindecker
committed
class ShowLinkSerializer(LinkSerializer):
class Meta:
model = ShowLink
class ShowSerializer(serializers.HyperlinkedModelSerializer):
links = HostLinkSerializer(many=True, required=False)
owners = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), many=True)
category = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(), many=True
)
hosts = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all(), many=True)
language = serializers.PrimaryKeyRelatedField(
queryset=Language.objects.all(), many=True
)
topic = serializers.PrimaryKeyRelatedField(queryset=Topic.objects.all(), many=True)
music_focus = serializers.PrimaryKeyRelatedField(
queryset=MusicFocus.objects.all(), many=True
type = serializers.PrimaryKeyRelatedField(queryset=Type.objects.all())
funding_category = serializers.PrimaryKeyRelatedField(
queryset=FundingCategory.objects.all()
)
predecessor = serializers.PrimaryKeyRelatedField(
queryset=Show.objects.all(), required=False, allow_null=True
)
thumbnails = serializers.SerializerMethodField()
def get_thumbnails(show) -> List[str]:
"""Returns thumbnails"""
thumbnails = []
if show.image.name and THUMBNAIL_SIZES:
for size in THUMBNAIL_SIZES:
thumbnails.append(show.image.crop[size].name)
return thumbnails
class Meta:
model = Show
fields = (
"id",
"name",
"slug",
"logo",
"short_description",
"description",
"email",
"type",
"funding_category",
"predecessor",
"cba_series_id",
"default_playlist_id",
"category",
"hosts",
"owners",
"language",
"topic",
"music_focus",
"thumbnails",
"is_active",
"is_public",
# ModelWithImageFields
"image",
"image_ppoi",
"image_width",
"image_height",
"image_credits",
"image_alt_text",
# ModelWithCreatedUpdatedFields
"created_at",
"created_by",
"updated_at",
"updated_by",
read_only_fields = (
"created_at",
"created_by",
"updated_at",
"updated_by",
)
def create(self, validated_data):
"""
Create and return a new Show instance, given the validated data.
"""
owners = validated_data.pop("owners")
category = validated_data.pop("category")
hosts = validated_data.pop("hosts")
language = validated_data.pop("language")
topic = validated_data.pop("topic")
music_focus = validated_data.pop("music_focus")
links_data = validated_data.pop("links", [])
show = Show.objects.create(**validated_data | self.context)
# Save many-to-many relationships

jackie / Andrea Ida Malkah Klaura
committed
show.owners.set(owners)
show.category.set(category)
show.hosts.set(hosts)
show.language.set(language)
show.topic.set(topic)
show.music_focus.set(music_focus)
for link_data in links_data:
ShowLink.objects.create(show=show, **link_data)
show.save()
return show
def update(self, instance, validated_data):
"""
Update and return an existing Show instance, given the validated data.
"""
instance.name = validated_data.get("name", instance.name)
instance.slug = validated_data.get("slug", instance.slug)
instance.image = validated_data.get("image", instance.image)
instance.image_ppoi = validated_data.get("image_ppoi", instance.image_ppoi)
instance.image_credits = validated_data.get(
"image_credits", instance.image_credits
)
instance.image_alt_text = validated_data.get(
"image_alt_text", instance.image_alt_text
)
instance.logo = validated_data.get("logo", instance.logo)
instance.short_description = validated_data.get(
"short_description", instance.short_description
)
instance.description = validated_data.get("description", instance.description)
instance.email = validated_data.get("email", instance.email)
instance.cba_series_id = validated_data.get(
"cba_series_id", instance.cba_series_id
)
instance.default_playlist_id = validated_data.get(
"default_playlist_id", instance.default_playlist_id
)
instance.type = validated_data.get("type", instance.type)
instance.funding_category = validated_data.get(
"funding_category", instance.funding_category
)
instance.predecessor = validated_data.get("predecessor", instance.predecessor)
instance.is_active = validated_data.get("is_active", instance.is_active)
instance.is_public = validated_data.get("is_public", instance.is_public)
instance.internal_note = validated_data.get(
"internal_note", instance.internal_note
)
instance.owners.set(validated_data.get("owners", instance.owners))
instance.category.set(validated_data.get("category", instance.category))
instance.hosts.set(validated_data.get("hosts", instance.hosts))
instance.language.set(validated_data.get("language", instance.language))
instance.topic.set(validated_data.get("topic", instance.topic))
instance.music_focus.set(
validated_data.get("music_focus", instance.music_focus)
)
instance.updated_by = self.context.get("updated_by")
if instance.links.count() > 0:
for link in instance.links.all():
link.delete(keep_parents=True)
if links_data := validated_data.get("links"):
for link_data in links_data:
ShowLink.objects.create(show=instance, **link_data)
instance.save()
return instance
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = (
"rrule",
"show",
"by_weekday",
"first_date",
"last_date",
"start_time",
"end_time",
"is_repetition",
"add_days_no",
"add_business_days_only",
"default_playlist_id",
)
class UnsavedScheduleSerializer(ScheduleSerializer):
id = serializers.IntegerField(allow_null=True)
class ScheduleInRequestSerializer(ScheduleSerializer):
dryrun = serializers.BooleanField(
write_only=True,
required=False,
help_text=(
"Whether to simulate the database changes. If true, no database changes will occur. "
"Instead a list of objects that would be created, updated and deleted if dryrun was "
"false will be returned."
),
)
fields = (
"rrule",
"show",
"by_weekday",
"first_date",
"last_date",
"start_time",
"end_time",
"is_repetition",
"add_days_no",
"add_business_days_only",
"default_playlist_id",
)
def create(self, validated_data):
"""Create and return a new Schedule instance, given the validated data."""
rrule = validated_data.pop("rrule")
show = validated_data.pop("show")
schedule = Schedule.objects.create(**validated_data)
schedule.rrule = rrule
schedule.show = show
schedule.save()
return schedule
def update(self, instance, validated_data):
"""Update and return an existing Schedule instance, given the validated data."""
instance.by_weekday = validated_data.get("by_weekday", instance.by_weekday)
instance.first_date = validated_data.get("first_date", instance.first_date)
instance.start_time = validated_data.get("start_time", instance.start_time)
instance.end_time = validated_data.get("end_time", instance.end_time)
instance.last_date = validated_data.get("last_date", instance.last_date)
instance.is_repetition = validated_data.get(
"is_repetition", instance.is_repetition
)
instance.default_playlist_id = validated_data.get(
"default_playlist_id", instance.default_playlist_id
)
instance.rrule = validated_data.get("rrule", instance.rrule)
instance.show = validated_data.get("show", instance.show)
instance.add_days_no = validated_data.get("add_days_no", instance.add_days_no)
instance.add_business_days_only = validated_data.get(
"add_business_days_only", instance.add_business_days_only
)
class CollisionSerializer(serializers.Serializer):
id = serializers.IntegerField()
start = serializers.DateTimeField()
end = serializers.DateTimeField()
playlist_id = serializers.IntegerField(allow_null=True)
show = serializers.IntegerField()
show_name = serializers.CharField()
repetition_of = serializers.IntegerField(allow_null=True)
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
schedule = serializers.IntegerField()
memo = serializers.CharField()
note_id = serializers.IntegerField(allow_null=True)
class ProjectedTimeSlotSerializer(serializers.Serializer):
hash = serializers.CharField()
start = serializers.DateTimeField()
end = serializers.DateTimeField()
collisions = CollisionSerializer(many=True)
error = serializers.CharField(allow_null=True)
solution_choices = serializers.ListField(
child=serializers.ChoiceField(SOLUTION_CHOICES)
)
class DryRunTimeSlotSerializer(serializers.Serializer):
id = serializers.PrimaryKeyRelatedField(
queryset=TimeSlot.objects.all(), allow_null=True
)
schedule = serializers.PrimaryKeyRelatedField(
queryset=Schedule.objects.all(), allow_null=True
)
playlist_id = serializers.IntegerField(allow_null=True)
start = serializers.DateField()
end = serializers.DateField()
repetition_of = serializers.IntegerField(allow_null=True)
memo = serializers.CharField()
class ScheduleCreateUpdateRequestSerializer(serializers.Serializer):
schedule = ScheduleInRequestSerializer()
solutions = serializers.DictField(
child=serializers.ChoiceField(SOLUTION_CHOICES), required=False
)
notes = serializers.DictField(child=serializers.IntegerField(), required=False)
playlists = serializers.DictField(child=serializers.IntegerField(), required=False)
class ScheduleResponseSerializer(serializers.Serializer):
projected = ProjectedTimeSlotSerializer(many=True)
solutions = serializers.DictField(child=serializers.ChoiceField(SOLUTION_CHOICES))
notes = serializers.DictField(child=serializers.IntegerField())
playlists = serializers.DictField(child=serializers.IntegerField())
schedule = ScheduleSerializer()
class ScheduleConflictResponseSerializer(ScheduleResponseSerializer):
schedule = UnsavedScheduleSerializer()
class ScheduleDryRunResponseSerializer(serializers.Serializer):
created = DryRunTimeSlotSerializer(many=True)
updated = DryRunTimeSlotSerializer(many=True)
deleted = DryRunTimeSlotSerializer(many=True)
class TimeSlotSerializer(serializers.ModelSerializer):
show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all())
schedule = serializers.PrimaryKeyRelatedField(queryset=Schedule.objects.all())
repetition_of = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all())
class Meta:
model = TimeSlot
fields = (
"schedule",
"show",
"start",
"end",
"memo",
"playlist_id",
"note_id",
)
def create(self, validated_data):
"""Create and return a new TimeSlot instance, given the validated data."""
return TimeSlot.objects.create(**validated_data)
def update(self, instance, validated_data):
"""Update and return an existing Show instance, given the validated data."""
# Only save certain fields
instance.memo = validated_data.get("memo", instance.memo)
instance.repetition_of = validated_data.get(
"repetition_of", instance.repetition_of
)
instance.playlist_id = validated_data.get("playlist_id", instance.playlist_id)
instance.save()
return instance
class NoteLinkSerializer(LinkSerializer):
class Meta:
model = NoteLink
class NoteSerializer(serializers.ModelSerializer):
links = NoteLinkSerializer(many=True, required=False)
timeslot = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all())
contributors = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all())
thumbnails = serializers.SerializerMethodField()
def get_thumbnails(note) -> List[str]:
"""Returns thumbnails"""
thumbnails = []
if note.image.name and THUMBNAIL_SIZES:
for size in THUMBNAIL_SIZES:
thumbnails.append(note.image.crop[size].name)
return thumbnails
class Meta:
model = Note
fields = (
"timeslot",
"contributors",
"owner",
"title",
"slug",
"summary",
"content",
"cba_id",
"tags",
# ModelWithImageFields
"image",
"image_ppoi",
"image_width",
"image_height",
"image_credits",
"image_alt_text",
# ModelWithCreatedUpdatedFields
"created_at",
"created_by",
"updated_at",
"updated_by",
def create(self, validated_data):
"""Create and return a new Note instance, given the validated data."""
links_data = validated_data.pop("links", [])
contributors = validated_data.pop("contributors", [])
# the creator of the note is the owner
validated_data["owner"] = self.context["user_id"]
note = Note.objects.create(**validated_data)
note.contributors.set(contributors)
if cba_id := validated_data.get("cba_id"):
if audio_url := get_audio_url(cba_id):
NoteLink.objects.create(note=note, description="CBA", url=audio_url)
for link_data in links_data:
NoteLink.objects.create(note=note, **link_data)
note.save()
try:
timeslot = TimeSlot.objects.get(pk=note.timeslot_id)
timeslot.note_id = note.id
timeslot.save(update_fields=["note_id"])
except ObjectDoesNotExist:
pass
return note
def update(self, instance, validated_data):
"""Update and return an existing Note instance, given the validated data."""
instance.show = validated_data.get("show", instance.show)
instance.timeslot = validated_data.get("timeslot", instance.timeslot)
instance.title = validated_data.get("title", instance.title)
instance.slug = validated_data.get("slug", instance.slug)
instance.summary = validated_data.get("summary", instance.summary)
instance.content = validated_data.get("content", instance.content)
instance.image = validated_data.get("image", instance.image)
instance.image_ppoi = validated_data.get("image_ppoi", instance.image_ppoi)
instance.image_credits = validated_data.get(
"image_credits", instance.image_credits
)
instance.image_alt_text = validated_data.get(
"image_alt_text", instance.image_alt_text
)
instance.cba_id = validated_data.get("cba_id", instance.cba_id)
instance.contributors.set(
validated_data.get("contributors", instance.contributors)
)
if instance.links.count() > 0:
for link in instance.links.all():
link.delete(keep_parents=True)
if cba_id := validated_data.get("cba_id"):
if audio_url := get_audio_url(cba_id):
NoteLink.objects.create(note=instance, description="CBA", url=audio_url)
if links_data := validated_data.get("links"):
for link_data in links_data:
NoteLink.objects.create(note=instance, **link_data)
instance.save()
# Remove existing note connections from timeslots
timeslots = TimeSlot.objects.filter(note_id=instance.id)
for ts in timeslots:
ts.note_id = None
ts.save(update_fields=["note_id"])
# Assign note to timeslot
try:
timeslot = TimeSlot.objects.get(pk=instance.timeslot.id)
timeslot.note_id = instance.id
timeslot.save(update_fields=["note_id"])
except ObjectDoesNotExist:
pass

jackie / Andrea Ida Malkah Klaura
committed
return instance