Source code for pretalx.schedule.models.slot

import datetime as dt
import re
import string
import uuid
from contextlib import suppress
from urllib.parse import urlparse

import pytz
from django.db import models
from django.utils.functional import cached_property
from django_scopes import ScopedManager
from i18nfield.fields import I18nCharField

from pretalx.common.mixins import LogMixin
from pretalx.common.urls import get_base_url

INSTANCE_IDENTIFIER = None
with suppress(Exception):
    from pretalx.common.models.settings import GlobalSettings

    INSTANCE_IDENTIFIER = GlobalSettings().get_instance_identifier()


[docs]class TalkSlot(LogMixin, models.Model): """The TalkSlot object is the scheduled version of a. :class:`~pretalx.submission.models.submission.Submission`. TalkSlots always belong to one submission and one :class:`~pretalx.schedule.models.schedule.Schedule`. :param is_visible: This parameter is set on schedule release. Only confirmed talks will be visible. """ submission = models.ForeignKey( to="submission.Submission", on_delete=models.PROTECT, related_name="slots", null=True, blank=True, # If the submission is empty, this is a break or similar event ) room = models.ForeignKey( to="schedule.Room", on_delete=models.PROTECT, related_name="talks", null=True, blank=True, ) schedule = models.ForeignKey( to="schedule.Schedule", on_delete=models.PROTECT, related_name="talks" ) is_visible = models.BooleanField(default=False) start = models.DateTimeField(null=True) end = models.DateTimeField(null=True) description = I18nCharField(null=True) objects = ScopedManager(event="schedule__event") def __str__(self): """Help when debugging.""" return f'TalkSlot(event={self.schedule.event.slug}, submission={getattr(self.submission, "title", None)}, schedule={self.schedule.version})' @cached_property def event(self): return self.submission.event @property def duration(self) -> int: """Returns the actual duration in minutes if the talk is scheduled, and the planned duration in minutes otherwise.""" if self.start and self.end: return int((self.end - self.start).total_seconds() / 60) if not self.submission: return None return self.submission.get_duration() @cached_property def export_duration(self): from pretalx.common.serialize import serialize_duration return serialize_duration(minutes=self.duration) @cached_property def pentabarf_export_duration(self): duration = dt.timedelta(minutes=self.duration) days = duration.days hours = duration.total_seconds() // 3600 - days * 24 minutes = duration.seconds // 60 % 60 return f"{hours:02}{minutes:02}00" @cached_property def real_end(self): """Guaranteed to provide a useful end datetime if ``start`` is set, even if ``end`` is empty.""" return self.end or ( self.start + dt.timedelta(minutes=self.duration) if self.start else None ) @cached_property def as_availability(self): """'Casts' a slot as. :class:`~pretalx.schedule.models.availability.Availability`, useful for availability arithmetic. """ from pretalx.schedule.models import Availability return Availability( start=self.start, end=self.real_end, )
[docs] def copy_to_schedule(self, new_schedule, save=True): """Create a new slot for the given. :class:`~pretalx.schedule.models.schedule.Schedule` with all other fields identical to this one. """ new_slot = TalkSlot(schedule=new_schedule) for field in [f for f in self._meta.fields if f.name not in ("id", "schedule")]: setattr(new_slot, field.name, getattr(self, field.name)) if save: new_slot.save() return new_slot
copy_to_schedule.alters_data = True
[docs] def is_same_slot(self, other_slot) -> bool: """Checks if both slots have the same room and start time.""" return self.room == other_slot.room and self.start == other_slot.start
@cached_property def id_suffix(self): if not self.event.settings.present_multiple_times: return "" all_slots = list( TalkSlot.objects.filter( submission_id=self.submission_id, schedule_id=self.schedule_id ) ) if len(all_slots) == 1: return "" return "-" + str(all_slots.index(self)) @cached_property def frab_slug(self): title = re.sub(r"\W+", "-", self.submission.title) legal_chars = string.ascii_letters + string.digits + "-" pattern = f"[^{legal_chars}]+" title = re.sub(pattern, "", title) title = title.lower() title = title.strip("_") return f"{self.event.slug}-{self.submission.pk}{self.id_suffix}-{title}" @cached_property def uuid(self): """A UUID5, calculated from the submission code and the instance identifier.""" global INSTANCE_IDENTIFIER if not INSTANCE_IDENTIFIER: from pretalx.common.models.settings import GlobalSettings INSTANCE_IDENTIFIER = GlobalSettings().get_instance_identifier() return uuid.uuid5(INSTANCE_IDENTIFIER, self.submission.code + self.id_suffix) def build_ical(self, calendar, creation_time=None, netloc=None): if not self.start or not self.end or not self.room or not self.submission: return creation_time = creation_time or dt.datetime.now(pytz.utc) netloc = netloc or urlparse(get_base_url(self.event)).netloc tz = pytz.timezone(self.submission.event.timezone) vevent = calendar.add("vevent") vevent.add( "summary" ).value = f"{self.submission.title} - {self.submission.display_speaker_names}" vevent.add("dtstamp").value = creation_time vevent.add("location").value = str(self.room.name) vevent.add("uid").value = "pretalx-{}-{}{}@{}".format( self.submission.event.slug, self.submission.code, self.id_suffix, netloc ) vevent.add("dtstart").value = self.start.astimezone(tz) vevent.add("dtend").value = self.end.astimezone(tz) vevent.add("description").value = self.submission.abstract or "" vevent.add("url").value = self.submission.urls.public.full()