import os from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist, ValidationError, MultipleObjectsReturned from django.core.urlresolvers import reverse from django.db import models from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from tinymce import models as tinymce_models from datetime import date, datetime, time, timedelta from dateutil.relativedelta import relativedelta from dateutil.rrule import rrule from utils import get_automation_id_choices, hash_file class BroadcastFormat(models.Model): format = models.CharField(_("Format"), max_length=32) slug = models.SlugField(_("Slug"), max_length=32, unique=True) color = models.CharField(_("Color"), max_length=7, default='#ffffff') text_color = models.CharField(_("Text color"), max_length=7, default='#000000') enabled = models.BooleanField(_("Enabled"), default=True) class Meta: ordering = ('format',) verbose_name = _("Broadcast format") verbose_name_plural = _("Broadcast formats") def admin_color(self): return u'%s/%s' % ( self.color, self.text_color, self.color, self.text_color) admin_color.short_description = _("Color") admin_color.allow_tags = True def __unicode__(self): return u'%s' % self.format class ShowInformation(models.Model): information = models.CharField(_("Information"), max_length=32) abbrev = models.CharField(_("Abbreviation"), max_length=4, unique=True) slug = models.SlugField(_("Slug"), max_length=32, unique=True) button = models.ImageField(_("Button image"), blank=True, null=True, upload_to='buttons') button_hover = models.ImageField(_("Button image (hover)"), blank=True, null=True, upload_to='buttons') big_button = models.ImageField(_("Big button image"), blank=True, null=True, upload_to='buttons') class Meta: ordering = ('information',) verbose_name = _("Show information") verbose_name_plural = _("Show information") def admin_buttons(self): buttons = [] if self.button: buttons.append(u'' % self.button.url) else: buttons.append(u'x') if self.button_hover: buttons.append(u'' % self.button_hover.url) else: buttons.append(u'x') if self.big_button: buttons.append(u'' % self.big_button.url) else: buttons.append(u'x') return ' '.join(buttons) admin_buttons.short_description = _("Buttons") admin_buttons.allow_tags = True def button_url(self): if self.button: return self.button.url else: return '/site_media/buttons/default-11.png' def button_hover_url(self): if self.button_hover: return self.button_hover.url else: return '/site_media/buttons/default-11.png' def big_button_url(self): if self.big_button: return self.big_button.url else: return '/site_media/buttons/default-17.png' def __unicode__(self): return u'%s' % self.information class ShowTopic(models.Model): topic = models.CharField(_("Show topic"), max_length=32) abbrev = models.CharField(_("Abbreviation"), max_length=4, unique=True) slug = models.SlugField(_("Slug"), max_length=32, unique=True) button = models.ImageField(_("Button image"), blank=True, null=True, upload_to='buttons') button_hover = models.ImageField(_("Button image (hover)"), blank=True, null=True, upload_to='buttons') big_button = models.ImageField(_("Big button image"), blank=True, null=True, upload_to='buttons') class Meta: ordering = ('topic',) verbose_name = _("Show topic") verbose_name_plural = _("Show topics") def admin_buttons(self): buttons = [] if self.button: buttons.append(u'' % self.button.url) else: buttons.append(u'x') if self.button_hover: buttons.append(u'' % self.button_hover.url) else: buttons.append(u'x') if self.big_button: buttons.append(u'' % self.big_button.url) else: buttons.append(u'x') return ' '.join(buttons) admin_buttons.short_description = _("Buttons") admin_buttons.allow_tags = True def button_url(self): if self.button: return self.button.url else: return '/site_media/buttons/default-11.png' def button_hover_url(self): if self.button_hover: return self.button_hover.url else: return '/site_media/buttons/default-11.png' def big_button_url(self): if self.big_button: return self.big_button.url else: return '/site_media/buttons/default-17.png' def __unicode__(self): return u'%s' % self.topic class MusicFocus(models.Model): focus = models.CharField(_("Focus"), max_length=32) abbrev = models.CharField(_("Abbreviation"), max_length=4, unique=True) slug = models.SlugField(_("Slug"), max_length=32, unique=True) button = models.ImageField(_("Button image"), blank=True, null=True, upload_to='buttons') button_hover = models.ImageField(_("Button image (hover)"), blank=True, null=True, upload_to='buttons') big_button = models.ImageField(_("Big button image"), blank=True, null=True, upload_to='buttons') class Meta: ordering = ('focus',) verbose_name = _("Music focus") verbose_name_plural = _("Music focus") def admin_buttons(self): buttons = [] if self.button: buttons.append(u'' % self.button.url) else: buttons.append(u'x') if self.button_hover: buttons.append(u'' % self.button_hover.url) else: buttons.append(u'x') if self.big_button: buttons.append(u'' % self.big_button.url) else: buttons.append(u'x') return ' '.join(buttons) admin_buttons.short_description = _("Buttons") admin_buttons.allow_tags = True def button_url(self): if self.button: return self.button.url else: return '/site_media/buttons/default-11.png' def button_hover_url(self): if self.button_hover: return self.button_hover.url else: return '/site_media/buttons/default-11.png' def big_button_url(self): if self.big_button: return self.big_button.url else: return '/site_media/buttons/default-17.png' def __unicode__(self): return u'%s' % self.focus class Language(models.Model): slug = models.SlugField(_("Slug"), max_length=32, unique=True) name = models.CharField(_("Name"), max_length=32) native_name = models.CharField(_("Native Name"), max_length=32) class Meta: ordering = ('name',) verbose_name = _("Language") verbose_name_plural = _("Languages") def __unicode__(self): return '%s' % self.name class Host(models.Model): name = models.CharField(_("Name"), max_length=128) is_always_visible = models.BooleanField(_("Is always visible"), default=False) email = models.EmailField(_("E-Mail"), blank=True) website = models.URLField(_("Website"), blank=True) class Meta: ordering = ('name',) verbose_name = _("Host") verbose_name_plural = _("Hosts") def __unicode__(self): return u'%s' % self.name def get_absolute_url(self): return reverse('host-detail', args=[str(self.id)]) def active_shows(self): return self.shows.filter(programslots__until__gt=datetime.today).distinct() def show_image_filename(instance, filename): instance.image.open() filename_base, filename_ext = os.path.splitext(filename) return "shows/%s/%s%s" % (instance.slug, hash_file(instance.image.file), filename_ext) class Show(models.Model): predecessor = models.ForeignKey('self', blank=True, null=True, related_name='successors', verbose_name=_("Predecessor")) hosts = models.ManyToManyField(Host, blank=True, related_name='shows', verbose_name=_("Hosts")) owners = models.ManyToManyField(User, blank=True, related_name='shows', verbose_name=_("Owners")) language = models.ManyToManyField(Language, blank=True, related_name='shows', verbose_name=_("Language")) broadcastformat = models.ForeignKey(BroadcastFormat, related_name='shows', verbose_name=_("Broadcast format")) showinformation = models.ManyToManyField(ShowInformation, blank=True, related_name='shows', verbose_name=_("Show information")) showtopic = models.ManyToManyField(ShowTopic, blank=True, related_name='shows', verbose_name=_("Show topic")) musicfocus = models.ManyToManyField(MusicFocus, blank=True, related_name='shows', verbose_name=_("Music focus")) name = models.CharField(_("Name"), max_length=255) slug = models.CharField(_("Slug"), max_length=255, unique=True) image = models.ImageField(_("Image"), max_length=350, blank=True, null=True, upload_to=show_image_filename, help_text=_( "optimal size 900x675 pixel or smaller, aspect ratio 4:3, file size should be smaller than 500kB")) short_description = models.CharField(_("Short description"), max_length=64) description = tinymce_models.HTMLField(_("Description"), blank=True, null=True) email = models.EmailField(_("E-Mail"), blank=True, null=True) website = models.URLField(_("Website"), blank=True, null=True) created = models.DateTimeField(auto_now_add=True, editable=False) last_updated = models.DateTimeField(auto_now=True, editable=False) class Meta: ordering = ('slug',) verbose_name = _("Show") verbose_name_plural = _("Shows") def __unicode__(self): return u'%04d | %s' % (self.id, self.name) def get_absolute_url(self): return reverse('show-detail', args=[self.slug]) def active_programslots(self): return self.programslots.filter(until__gt=date.today()) class RRule(models.Model): FREQ_CHOICES = ( (1, _("Monthly")), (2, _("Weekly")), (3, _("Daily")), ) BYSETPOS_CHOICES = ( (1, _("First")), (2, _("Second")), (3, _("Third")), (4, _("Fourth")), (5, _("Fifth")), (-1, _("Last")), ) name = models.CharField(_("Name"), max_length=32, unique=True) freq = models.IntegerField(_("Frequency"), choices=FREQ_CHOICES) interval = models.IntegerField(_("Interval"), default=1) bysetpos = models.IntegerField(_("Set position"), blank=True, choices=BYSETPOS_CHOICES, null=True) count = models.IntegerField(_("Count"), blank=True, null=True) class Meta: ordering = ('-freq', 'interval', 'bysetpos') verbose_name = _("Recurrence rule") verbose_name_plural = _("Recurrence rules") def __unicode__(self): return u'%s' % self.name class ProgramSlot(models.Model): BYWEEKDAY_CHOICES = ( (0, _("Monday")), (1, _("Tuesday")), (2, _("Wednesday")), (3, _("Thursday")), (4, _("Friday")), (5, _("Saturday")), (6, _("Sunday")), ) rrule = models.ForeignKey(RRule, related_name='programslots', verbose_name=_("Recurrence rule")) byweekday = models.IntegerField(_("Weekday"), choices=BYWEEKDAY_CHOICES) show = models.ForeignKey(Show, related_name='programslots', verbose_name=_("Show")) dstart = models.DateField(_("First date")) tstart = models.TimeField(_("Start time")) tend = models.TimeField(_("End time")) until = models.DateField(_("Last date")) is_repetition = models.BooleanField(_("Is repetition"), default=False) automation_id = models.IntegerField(_("Automation ID"), blank=True, null=True, choices=get_automation_id_choices()) created = models.DateTimeField(auto_now_add=True, editable=False) last_updated = models.DateTimeField(auto_now=True, editable=False) class Meta: ordering = ('dstart', 'tstart') unique_together = ('rrule', 'byweekday', 'dstart', 'tstart') verbose_name = _("Program slot") verbose_name_plural = _("Program slots") def __unicode__(self): weekday = self.BYWEEKDAY_CHOICES[self.byweekday][1] tend = self.tend.strftime('%H:%M') dstart = self.dstart.strftime('%d. %b %Y') tstart = self.tstart.strftime('%H:%M') if self.rrule.freq == 0: return u'%s, %s - %s' % (dstart, tstart, tend) if self.rrule.freq == 3: return u'%s, %s - %s' % (self.rrule, tstart, tend) else: return u'%s, %s, %s - %s' % (weekday, self.rrule, tstart, tend) def save(self, *args, **kwargs): if self.pk: old = ProgramSlot.objects.get(pk=self.pk) if self.rrule != old.rrule: raise ValidationError(u"Recurrence rule cannot ba changed") if self.byweekday != old.byweekday: raise ValidationError(u"Weekday cannot be changed") if self.show != old.show: raise ValidationError(u"Show cannot be changed") if self.dstart != old.dstart: raise ValidationError(u"First date cannot ba changed") if self.tstart != old.tstart: raise ValidationError(u"Start time cannot be changed") if self.tend != old.tend: raise ValidationError(u"End time cannot be changed") if self.is_repetition != old.is_repetition: raise ValidationError(u"Is repetition cannot be changed") else: old = False super(ProgramSlot, self).save(*args, **kwargs) if self.rrule.freq == 0: byweekday_start = None byweekday_end = None elif self.rrule.freq == 3: byweekday_start = (0, 1, 2, 3, 4, 5, 6) byweekday_end = (0, 1, 2, 3, 4, 5, 6) else: byweekday_start = int(self.byweekday) if self.tend < self.tstart: if self.byweekday < 6: byweekday_end = int(self.byweekday + 1) else: byweekday_end = 0 else: byweekday_end = int(self.byweekday) if self.tend < self.tstart: dend = self.dstart + timedelta(days=+1) else: dend = self.dstart starts = list(rrule(freq=self.rrule.freq, dtstart=datetime.combine(self.dstart, self.tstart), interval=self.rrule.interval, until=self.until + relativedelta(days=+1), bysetpos=self.rrule.bysetpos, byweekday=byweekday_start)) ends = list(rrule(freq=self.rrule.freq, dtstart=datetime.combine(dend, self.tend), interval=self.rrule.interval, until=self.until + relativedelta(days=+1), bysetpos=self.rrule.bysetpos, byweekday=byweekday_end)) if not old: for k in range(min(len(starts), len(ends))): TimeSlot.objects.create(programslot=self, start=starts[k], end=ends[k]) elif self.until > old.until: for k in range(min(len(starts), len(ends))): if starts[k].date() > old.until: TimeSlot.objects.create(programslot=self, start=starts[k], end=ends[k]) class TimeSlotManager(models.Manager): @staticmethod def get_or_create_current(): try: return TimeSlot.objects.get(start__lte=datetime.now, end__gt=datetime.now) except MultipleObjectsReturned: return TimeSlot.objects.filter(start__lte=datetime.now, end__gt=datetime.now)[0] except ObjectDoesNotExist: once = RRule.objects.get(pk=1) today = date.today().weekday() default = Show.objects.get(pk=1) previous_timeslot = TimeSlot.objects.filter(end__lte=datetime.now).order_by('-start')[0] next_timeslot = TimeSlot.objects.filter(start__gte=datetime.now)[0] dstart, tstart = previous_timeslot.end.date(), previous_timeslot.end.time() until, tend = next_timeslot.start.date(), next_timeslot.start.time() new_programslot = ProgramSlot(rrule=once, byweekday=today, show=default, dstart=dstart, tstart=tstart, tend=tend, until=until) try: new_programslot.validate_unique() new_programslot.save() except ValidationError: pass else: return new_programslot.timeslots.all()[0] @staticmethod def get_day_timeslots(day): today = datetime.combine(day, time(6, 0)) tomorrow = today + timedelta(days=1) return TimeSlot.objects.filter(Q(start__lte=today, end__gte=today) | Q(start__gt=today, start__lt=tomorrow)).exclude(end=today) @staticmethod def get_24h_timeslots(start): end = start + timedelta(hours=24) return TimeSlot.objects.filter(Q(start__lte=start, end__gte=start) | Q(start__gt=start, start__lt=end)).exclude(end=start) class TimeSlot(models.Model): programslot = models.ForeignKey(ProgramSlot, related_name='timeslots', verbose_name=_("Program slot")) start = models.DateTimeField(_("Start time"), unique=True) end = models.DateTimeField(_("End time")) show = models.ForeignKey(Show, editable=False, related_name='timeslots') objects = TimeSlotManager() class Meta: ordering = ('start', 'end') verbose_name = _("Time slot") verbose_name_plural = _("Time slots") def __unicode__(self): start = self.start.strftime('%d.%m.%Y %H:%M') end = self.end.strftime('%H:%M') return u'%s - %s | %s' % (start, end, self.show.name) def save(self, *args, **kwargs): self.show = self.programslot.show super(TimeSlot, self).save(*args, **kwargs) def get_absolute_url(self): return reverse('timeslot-detail', args=[str(self.id)]) def note_image_filename(instance, filename): instance.image.open() filename_base, filename_ext = os.path.splitext(filename) return "notes/%s/%s%s" % (instance.show.slug, hash_file(instance.image.file), filename_ext) class Note(models.Model): STATUS_CHOICES = ( (0, _("Cancellation")), (1, _("Recommendation")), (2, _("Repetition")), ) timeslot = models.OneToOneField(TimeSlot, verbose_name=_("Time slot")) title = models.CharField(_("Title"), max_length=128) image = models.ImageField(_("Image"), max_length=350, blank=True, null=True, upload_to=note_image_filename, help_text=_("optimal size 900x675 pixel or smaller, aspect ratio 4:3, file size should be smaller than 500kB")) content = tinymce_models.HTMLField(_("Content")) status = models.IntegerField(_("Status"), choices=STATUS_CHOICES, default=1) start = models.DateTimeField(editable=False) show = models.ForeignKey(Show, editable=False, related_name='notes') created = models.DateTimeField(auto_now_add=True, editable=False) last_updated = models.DateTimeField(auto_now=True, editable=False) class Meta: ordering = ('timeslot',) verbose_name = _("Note") verbose_name_plural = _("Notes") def __unicode__(self): return u'%s - %s' % (self.title, self.timeslot) def save(self, *args, **kwargs): self.start = self.timeslot.start self.show = self.timeslot.programslot.show super(Note, self).save(*args, **kwargs)