]> git.0d.be Git - django-panik-nonstop.git/blobdiff - nonstop/models.py
trivial: fix misindentation
[django-panik-nonstop.git] / nonstop / models.py
index 0df211e471bea3da0429d24c68575b858cf7f41b..06b6516b32b86aa4264ca3f9926c21e4ba02109f 100644 (file)
@@ -1,8 +1,30 @@
+import datetime
+import os
+import random
+
+from django.conf import settings
 from django.core.urlresolvers import reverse
 from django.db import models
+from django.db.models.signals import post_delete, post_save
+from django.dispatch import receiver
+from django.utils.timezone import now
 from django.utils.translation import ugettext_lazy as _
 
-REMOTE_BASE_PATH = '/srv/soma/nonstop/'
+from .app_settings import app_settings
+
+
+TRANCHE_SLUG_DIR_MAPPING = {
+    'acouphene': 'Acouphene',
+    'biodiversite': 'Biodiversite',
+    'l-heure-de-pointe': 'Heure_de_pointe',
+    'hop-bop-co': 'Hop_Bop_and_co',
+    'la-panique': 'la_panique',
+    'le-mange-disque': 'Mange_Disque',
+    'matin-tranquille': 'Matins_tranquilles',
+    'reveries': 'Reveries',
+    'up-beat-tempo': 'Up_Beat_Tempo',
+}
+
 
 
 class Artist(models.Model):
@@ -11,6 +33,9 @@ class Artist(models.Model):
     class Meta:
         ordering = ['name']
 
+    def __str__(self):
+        return self.name
+
     def get_absolute_url(self):
         return reverse('artist-view', kwargs={'pk': self.id})
 
@@ -18,6 +43,12 @@ class Artist(models.Model):
         return SomaLogLine.objects.filter(filepath__track__artist=self
                 ).exclude(on_air=False).order_by('-play_timestamp')
 
+    def active_tracks(self):
+        return self.track_set.filter(nonstop_zones__isnull=False).distinct().order_by('title')
+
+    def available_tracks(self):
+        return self.track_set.filter(nonstop_zones__isnull=True).order_by('title')
+
 
 class Album(models.Model):
     name = models.CharField(_('Name'), max_length=255)
@@ -26,7 +57,9 @@ class Album(models.Model):
 LANGUAGES = [
     ('en', _('English')),
     ('fr', _('French')),
-    ('nl', _('Dutch'))
+    ('nl', _('Dutch')),
+    ('other', _('Other')),
+    ('na', _('Not applicable')),
 ]
 
 class Track(models.Model):
@@ -34,10 +67,22 @@ class Track(models.Model):
     artist = models.ForeignKey(Artist, null=True)
     album = models.ForeignKey(Album, null=True)
     instru = models.BooleanField(_('Instru'), default=False)
-    language = models.CharField(max_length=3,
+    language = models.CharField(max_length=10,
             choices=LANGUAGES, blank=True)
     sabam = models.BooleanField('SABAM', default=True)
     cfwb = models.BooleanField('CFWB', default=False)
+    nonstop_zones = models.ManyToManyField('emissions.Nonstop', blank=True)
+
+    creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
+    added_to_nonstop_timestamp = models.DateTimeField(null=True)
+    uploader = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
+    duration = models.DurationField(_('Duration'), null=True)
+
+    class Meta:
+        ordering = ['creation_timestamp']
+
+    def __str__(self):
+        return 'Track %s (%s)' % (self.title, self.artist or 'unknown')
 
     def get_absolute_url(self):
         return reverse('track-view', kwargs={'pk': self.id})
@@ -46,15 +91,71 @@ class Track(models.Model):
         return SomaLogLine.objects.filter(filepath__track=self
                 ).exclude(on_air=False).order_by('-play_timestamp')
 
+    def file_path(self):
+        nfile = None
+        for nfile in self.nonstopfile_set.all().order_by('creation_timestamp'):
+            if os.path.exists(nfile.get_local_filepath()):
+                return nfile.get_local_filepath()
+        if nfile:
+            return nfile.get_local_filepath()
+        return None
+
+    def file_exists(self):
+        file_path = self.file_path()
+        if not file_path:
+            return False
+        try:
+            return os.path.exists(file_path)
+        except AttributeError:
+            return False
+
+    def sync_nonstop_zones(self):
+        current_zones = self.nonstop_zones.all()
+        if current_zones.count():
+            if not self.added_to_nonstop_timestamp:
+                self.added_to_nonstop_timestamp = now()
+                self.save()
+        else:
+            self.added_to_nonstop_timestamp = None
+            self.save()
+
+        if not self.file_exists():
+            return
+        nonstop_file = self.nonstopfile_set.order_by('creation_timestamp').last()
+        filename = nonstop_file.filename
+        from emissions.models import Nonstop
+
+        for zone in Nonstop.objects.all():
+            if not zone.slug in TRANCHE_SLUG_DIR_MAPPING:
+                continue
+            zone_dir = TRANCHE_SLUG_DIR_MAPPING[zone.slug]
+            zone_path = os.path.join(app_settings.LOCAL_BASE_PATH, 'Tranches', zone_dir, filename)
+            if zone in current_zones:
+                if not os.path.exists(zone_path):
+                    os.symlink(os.path.join('..', '..', nonstop_file.short), zone_path)
+            else:
+                if os.path.exists(zone_path):
+                    os.unlink(zone_path)
+
 
 class NonstopFile(models.Model):
     filepath = models.CharField(_('Filepath'), max_length=255)
+    filename = models.CharField(_('Filename'), max_length=255, null=True)
     creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
     track = models.ForeignKey(Track, null=True)
 
     @property
     def short(self):
-        return self.filepath[len(REMOTE_BASE_PATH):]
+        return self.filepath[len(app_settings.REMOTE_BASE_PATH):]
+
+    def set_track_filepath(self, filepath):
+        self.filepath = os.path.join(app_settings.REMOTE_BASE_PATH, 'tracks', filepath)
+        self.filename = os.path.basename(filepath)
+
+    def get_local_filepath(self):
+        if not self.short:
+            return None
+        return os.path.join(app_settings.LOCAL_BASE_PATH, self.short)
 
 
 class SomaLogLine(models.Model):
@@ -63,6 +164,252 @@ class SomaLogLine(models.Model):
         verbose_name_plural = _('Soma log lines')
         ordering = ['play_timestamp']
 
-    filepath = models.ForeignKey(NonstopFile)
+    filepath = models.ForeignKey(NonstopFile, null=True)
+    track = models.ForeignKey(Track, null=True)
     play_timestamp = models.DateTimeField()
     on_air = models.NullBooleanField('On Air')
+
+    def get_track(self):
+        if self.track_id:
+            return self.track
+        if self.filepath_id:
+            return self.filepath.track
+        return None
+
+
+class Jingle(models.Model):
+    class Meta:
+        verbose_name = _('Jingle')
+        verbose_name_plural = _('Jingles')
+        ordering = ['label']
+
+    label = models.CharField(_('Label'), max_length=100)
+    filepath = models.CharField(_('File Path'), max_length=255)
+    duration = models.DurationField(_('Duration'), null=True, blank=True)
+    default_for_initial_diffusions = models.BooleanField(_('Default for initial diffusions'), default=False)
+    default_for_reruns = models.BooleanField(_('Default for reruns'), default=False)
+    default_for_streams = models.BooleanField(_('Default for streams'), default=False)
+
+    def __str__(self):
+        return self.label
+
+    @property
+    def short(self):
+        # property for compatibility with Track model
+        # for jingles self.filepath is actually only the last part of the path,
+        # ex: jingles panik/H_marimba_RP_chucho_zoe.wav
+        return self.filepath
+
+    def file_path(self):
+        # for compatibility with Track model method
+        return self.get_local_filepath()
+
+    def get_local_filepath(self):
+        if not self.short:
+            return None
+        return os.path.join(app_settings.LOCAL_BASE_PATH, app_settings.JINGLES_PREFIX, self.short)
+
+    @property
+    def title(self):
+        return self.label
+
+
+class Stream(models.Model):
+    class Meta:
+        verbose_name = _('Stream')
+        verbose_name_plural = _('Streams')
+        ordering = ['label']
+
+    label = models.CharField(_('Label'), max_length=100)
+    url = models.URLField(_('URL'), max_length=255)
+
+    def __str__(self):
+        return self.label
+
+
+class ScheduledDiffusion(models.Model):
+    class Meta:
+        verbose_name = _('Scheduled diffusion')
+        verbose_name_plural = _('Scheduled diffusions')
+
+    diffusion = models.ForeignKey('emissions.Diffusion', null=True, blank=True, on_delete=models.SET_NULL)
+    jingle = models.ForeignKey(Jingle, null=True, blank=True)
+    stream = models.ForeignKey(Stream, null=True, blank=True)
+    creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
+    added_to_nonstop_timestamp = models.DateTimeField(null=True)
+    auto_delayed = models.BooleanField(default=False)
+
+    def __str__(self):
+        return 'Diffusion of %s' % self.diffusion
+
+    @property
+    def datetime(self):
+        return self.diffusion.datetime
+
+    @property
+    def end_datetime(self):
+        if self.is_stream():
+            return self.diffusion.end_datetime
+        dt = self.diffusion.datetime
+        if self.jingle and self.jingle.duration:
+            dt += self.jingle.duration
+        dt += datetime.timedelta(seconds=self.soundfile.duration)
+        return dt
+
+    @property
+    def soundfile(self):
+        return self.diffusion.episode.soundfile_set.filter(fragment=False).first()
+
+    @property
+    def duration(self):
+        return (self.end_datetime - self.datetime).seconds
+
+    @property
+    def episode(self):
+        return self.diffusion.episode
+
+    @property
+    def soma_id(self):
+        if self.is_stream():
+            return '[stream:%s]' % self.id
+        else:
+            return '[sound:%s]' % self.id
+
+    def is_stream(self):
+        return bool(self.stream_id)
+
+    def get_jingle_filepath(self):
+        return self.jingle.get_local_filepath() if self.jingle_id else None
+
+    def file_path(self):
+        # TODO, copy and stuff
+        return self.soundfile.file.path
+
+
+class NonstopZoneSettings(models.Model):
+    nonstop = models.ForeignKey('emissions.Nonstop', on_delete=models.CASCADE)
+    intro_jingle = models.ForeignKey(Jingle, blank=True, null=True, related_name='+')
+    jingles = models.ManyToManyField(Jingle, blank=True)
+
+    def __str__(self):
+        return str(self.nonstop)
+
+
+class RecurringStreamDiffusion(models.Model):
+    schedule = models.ForeignKey('emissions.Schedule', on_delete=models.CASCADE)
+    jingle = models.ForeignKey(Jingle, null=True, blank=True)
+    stream = models.ForeignKey(Stream)
+    is_active = models.BooleanField('Active', default=True)
+
+    def __str__(self):
+        return _('Recurring Stream for %s') % self.schedule
+
+
+class RecurringOccurenceMixin:
+
+    @property
+    def jingle(self):
+        return self.diffusion.jingle
+
+    @property
+    def jingle_id(self):
+        return self.diffusion.jingle_id
+
+    @property
+    def duration(self):
+        return self.diffusion.schedule.get_duration() * 60
+
+    @property
+    def end_datetime(self):
+        return self.datetime + datetime.timedelta(minutes=self.diffusion.schedule.get_duration())
+
+    def get_jingle_filepath(self):
+        return self.diffusion.jingle.get_local_filepath() if self.diffusion.jingle_id else None
+
+
+class RecurringStreamOccurence(models.Model, RecurringOccurenceMixin):
+    diffusion = models.ForeignKey(RecurringStreamDiffusion, on_delete=models.CASCADE)
+    datetime = models.DateTimeField(_('Date/time'), db_index=True)
+
+    def __str__(self):
+        return 'Recurring stream of %s' % self.diffusion.stream
+
+    @property
+    def stream(self):
+        return self.diffusion.stream
+
+    def is_stream(self):
+        return True
+
+
+class RecurringRandomDirectoryDiffusion(models.Model):
+    # between soundfiles and nonstop zones, this is used for the "mix
+    # deliveries" segment during weekend nights.
+    schedule = models.ForeignKey('emissions.Schedule', on_delete=models.CASCADE)
+    jingle = models.ForeignKey(Jingle, null=True, blank=True)
+    directory = models.CharField(_('Directory'), max_length=255)
+    is_active = models.BooleanField('Active', default=True)
+
+    def __str__(self):
+        return _('Recurring Random Directory for %s') % self.schedule
+
+
+class RecurringRandomDirectoryOccurence(models.Model, RecurringOccurenceMixin):
+    diffusion = models.ForeignKey(RecurringRandomDirectoryDiffusion, on_delete=models.CASCADE)
+    datetime = models.DateTimeField(_('Date/time'), db_index=True)
+
+    def is_stream(self):
+        return False
+
+    def file_path(self):
+        directory = self.diffusion.directory
+        return os.path.join(directory, random.choice(os.listdir(directory)))
+
+
+@receiver(post_delete)
+def remove_soundfile(sender, instance=None, **kwargs):
+    from emissions.models import SoundFile
+    if not issubclass(sender, SoundFile):
+        return
+    ScheduledDiffusion.objects.filter(
+            diffusion__episode_id=instance.episode_id,
+            stream_id=None).update(
+            diffusion=None)
+
+
+@receiver(post_save)
+def save_soundfile(sender, instance=None, **kwargs):
+    if not app_settings.AUTO_SCHEDULE:
+        return
+    from emissions.models import SoundFile
+    if not issubclass(sender, SoundFile):
+        return
+    if instance.fragment:
+        return
+    if not instance.duration:
+        return
+    for i, diffusion in enumerate(instance.episode.diffusion_set.all()):
+        if i == 0:
+            jingle_qs = Jingle.objects.filter(default_for_initial_diffusions=True)
+        else:
+            jingle_qs = Jingle.objects.filter(default_for_reruns=True)
+        ScheduledDiffusion.objects.get_or_create(
+                diffusion=diffusion, stream_id=None,
+                defaults={'jingle': jingle_qs.order_by('?').first()})
+
+
+@receiver(post_save)
+def save_diffusion(sender, instance=None, **kwargs):
+    if not app_settings.AUTO_SCHEDULE:
+        return
+    from emissions.models import Diffusion
+    if not issubclass(sender, Diffusion):
+        return
+    if instance.episode.soundfile_set.filter(fragment=False).exists():
+        if Diffusion.objects.filter(episode=instance.episode, datetime__lt=instance.datetime).exists():
+            # rerun
+            jingle_qs = Jingle.objects.filter(default_for_reruns=True)
+        else:
+            jingle_qs = Jingle.objects.filter(default_for_initial_diffusions=True)
+        ScheduledDiffusion.objects.get_or_create(diffusion=instance,
+                stream_id=None, defaults={'jingle': jingle_qs.order_by('?').first()})