5 from django.conf import settings
6 from django.core.urlresolvers import reverse
7 from django.db import models
8 from django.db.models.signals import post_delete, post_save
9 from django.dispatch import receiver
10 from django.utils.timezone import now
11 from django.utils.translation import ugettext_lazy as _
13 from .app_settings import app_settings
16 TRANCHE_SLUG_DIR_MAPPING = {
17 'acouphene': 'Acouphene',
18 'biodiversite': 'Biodiversite',
19 'l-heure-de-pointe': 'Heure_de_pointe',
20 'hop-bop-co': 'Hop_Bop_and_co',
21 'la-panique': 'la_panique',
22 'le-mange-disque': 'Mange_Disque',
23 'matin-tranquille': 'Matins_tranquilles',
24 'reveries': 'Reveries',
25 'up-beat-tempo': 'Up_Beat_Tempo',
30 class Artist(models.Model):
31 name = models.CharField(_('Name'), max_length=255)
39 def get_absolute_url(self):
40 return reverse('artist-view', kwargs={'pk': self.id})
42 def recent_diffusions(self):
43 return SomaLogLine.objects.filter(filepath__track__artist=self
44 ).exclude(on_air=False).order_by('-play_timestamp')
46 def active_tracks(self):
47 return self.track_set.filter(nonstop_zones__isnull=False).distinct().order_by('title')
49 def available_tracks(self):
50 return self.track_set.filter(nonstop_zones__isnull=True).order_by('title')
53 class Album(models.Model):
54 name = models.CharField(_('Name'), max_length=255)
61 ('other', _('Other')),
62 ('na', _('Not applicable')),
65 class Track(models.Model):
66 title = models.CharField(_('Title'), max_length=255)
67 artist = models.ForeignKey(Artist, null=True)
68 album = models.ForeignKey(Album, null=True)
69 instru = models.BooleanField(_('Instru'), default=False)
70 language = models.CharField(max_length=10,
71 choices=LANGUAGES, blank=True)
72 sabam = models.BooleanField('SABAM', default=True)
73 cfwb = models.BooleanField('CFWB', default=False)
74 nonstop_zones = models.ManyToManyField('emissions.Nonstop', blank=True)
76 creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
77 added_to_nonstop_timestamp = models.DateTimeField(null=True)
78 uploader = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
79 duration = models.DurationField(_('Duration'), null=True)
82 ordering = ['creation_timestamp']
85 return 'Track %s (%s)' % (self.title, self.artist or 'unknown')
87 def get_absolute_url(self):
88 return reverse('track-view', kwargs={'pk': self.id})
90 def recent_diffusions(self):
91 return SomaLogLine.objects.filter(filepath__track=self
92 ).exclude(on_air=False).order_by('-play_timestamp')
96 for nfile in self.nonstopfile_set.all().order_by('creation_timestamp'):
97 if os.path.exists(nfile.get_local_filepath()):
98 return nfile.get_local_filepath()
100 return nfile.get_local_filepath()
103 def file_exists(self):
104 file_path = self.file_path()
108 return os.path.exists(file_path)
109 except AttributeError:
112 def sync_nonstop_zones(self):
113 current_zones = self.nonstop_zones.all()
114 if current_zones.count():
115 if not self.added_to_nonstop_timestamp:
116 self.added_to_nonstop_timestamp = now()
119 self.added_to_nonstop_timestamp = None
122 if not self.file_exists():
124 nonstop_file = self.nonstopfile_set.order_by('creation_timestamp').last()
125 filename = nonstop_file.filename
126 from emissions.models import Nonstop
128 for zone in Nonstop.objects.all():
129 if not zone.slug in TRANCHE_SLUG_DIR_MAPPING:
131 zone_dir = TRANCHE_SLUG_DIR_MAPPING[zone.slug]
132 zone_path = os.path.join(app_settings.LOCAL_BASE_PATH, 'Tranches', zone_dir, filename)
133 if zone in current_zones:
134 if not os.path.exists(zone_path):
135 os.symlink(os.path.join('..', '..', nonstop_file.short), zone_path)
137 if os.path.exists(zone_path):
141 class NonstopFile(models.Model):
142 filepath = models.CharField(_('Filepath'), max_length=255)
143 filename = models.CharField(_('Filename'), max_length=255, null=True)
144 creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
145 track = models.ForeignKey(Track, null=True)
149 return self.filepath[len(app_settings.REMOTE_BASE_PATH):]
151 def set_track_filepath(self, filepath):
152 self.filepath = os.path.join(app_settings.REMOTE_BASE_PATH, 'tracks', filepath)
153 self.filename = os.path.basename(filepath)
155 def get_local_filepath(self):
158 return os.path.join(app_settings.LOCAL_BASE_PATH, self.short)
161 class SomaLogLine(models.Model):
163 verbose_name = _('Soma log line')
164 verbose_name_plural = _('Soma log lines')
165 ordering = ['play_timestamp']
167 filepath = models.ForeignKey(NonstopFile, null=True)
168 track = models.ForeignKey(Track, null=True)
169 play_timestamp = models.DateTimeField()
170 on_air = models.NullBooleanField('On Air')
176 return self.filepath.track
180 class Jingle(models.Model):
182 verbose_name = _('Jingle')
183 verbose_name_plural = _('Jingles')
186 label = models.CharField(_('Label'), max_length=100)
187 filepath = models.CharField(_('File Path'), max_length=255)
188 duration = models.DurationField(_('Duration'), null=True, blank=True)
189 default_for_initial_diffusions = models.BooleanField(_('Default for initial diffusions'), default=False)
190 default_for_reruns = models.BooleanField(_('Default for reruns'), default=False)
191 default_for_streams = models.BooleanField(_('Default for streams'), default=False)
198 # property for compatibility with Track model
199 # for jingles self.filepath is actually only the last part of the path,
200 # ex: jingles panik/H_marimba_RP_chucho_zoe.wav
204 # for compatibility with Track model method
205 return self.get_local_filepath()
207 def get_local_filepath(self):
210 return os.path.join(app_settings.LOCAL_BASE_PATH, app_settings.JINGLES_PREFIX, self.short)
217 class Stream(models.Model):
219 verbose_name = _('Stream')
220 verbose_name_plural = _('Streams')
223 label = models.CharField(_('Label'), max_length=100)
224 url = models.URLField(_('URL'), max_length=255)
230 class ScheduledDiffusion(models.Model):
232 verbose_name = _('Scheduled diffusion')
233 verbose_name_plural = _('Scheduled diffusions')
235 diffusion = models.ForeignKey('emissions.Diffusion', null=True, blank=True, on_delete=models.SET_NULL)
236 jingle = models.ForeignKey(Jingle, null=True, blank=True)
237 stream = models.ForeignKey(Stream, null=True, blank=True)
238 creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
239 added_to_nonstop_timestamp = models.DateTimeField(null=True)
240 auto_delayed = models.BooleanField(default=False)
243 return 'Diffusion of %s' % self.diffusion
247 return self.diffusion.datetime
250 def end_datetime(self):
252 return self.diffusion.end_datetime
253 dt = self.diffusion.datetime
254 if self.jingle and self.jingle.duration:
255 dt += self.jingle.duration
256 dt += datetime.timedelta(seconds=self.soundfile.duration)
261 return self.diffusion.episode.soundfile_set.filter(fragment=False).first()
265 return (self.end_datetime - self.datetime).seconds
269 return self.diffusion.episode
274 return '[stream:%s]' % self.id
276 return '[sound:%s]' % self.id
279 return bool(self.stream_id)
281 def get_jingle_filepath(self):
282 return self.jingle.get_local_filepath() if self.jingle_id else None
285 # TODO, copy and stuff
286 return self.soundfile.file.path
289 class NonstopZoneSettings(models.Model):
290 nonstop = models.ForeignKey('emissions.Nonstop', on_delete=models.CASCADE)
291 intro_jingle = models.ForeignKey(Jingle, blank=True, null=True, related_name='+')
292 jingles = models.ManyToManyField(Jingle, blank=True)
295 return str(self.nonstop)
298 class RecurringStreamDiffusion(models.Model):
299 schedule = models.ForeignKey('emissions.Schedule', on_delete=models.CASCADE)
300 jingle = models.ForeignKey(Jingle, null=True, blank=True)
301 stream = models.ForeignKey(Stream)
302 is_active = models.BooleanField('Active', default=True)
305 return _('Recurring Stream for %s') % self.schedule
308 class RecurringOccurenceMixin:
312 return self.diffusion.jingle
316 return self.diffusion.jingle_id
320 return self.diffusion.schedule.get_duration() * 60
323 def end_datetime(self):
324 return self.datetime + datetime.timedelta(minutes=self.diffusion.schedule.get_duration())
326 def get_jingle_filepath(self):
327 return self.diffusion.jingle.get_local_filepath() if self.diffusion.jingle_id else None
330 class RecurringStreamOccurence(models.Model, RecurringOccurenceMixin):
331 diffusion = models.ForeignKey(RecurringStreamDiffusion, on_delete=models.CASCADE)
332 datetime = models.DateTimeField(_('Date/time'), db_index=True)
335 return 'Recurring stream of %s' % self.diffusion.stream
339 return self.diffusion.stream
345 class RecurringRandomDirectoryDiffusion(models.Model):
346 # between soundfiles and nonstop zones, this is used for the "mix
347 # deliveries" segment during weekend nights.
348 schedule = models.ForeignKey('emissions.Schedule', on_delete=models.CASCADE)
349 jingle = models.ForeignKey(Jingle, null=True, blank=True)
350 directory = models.CharField(_('Directory'), max_length=255)
351 is_active = models.BooleanField('Active', default=True)
354 return _('Recurring Random Directory for %s') % self.schedule
357 class RecurringRandomDirectoryOccurence(models.Model, RecurringOccurenceMixin):
358 diffusion = models.ForeignKey(RecurringRandomDirectoryDiffusion, on_delete=models.CASCADE)
359 datetime = models.DateTimeField(_('Date/time'), db_index=True)
365 directory = self.diffusion.directory
366 return os.path.join(directory, random.choice(os.listdir(directory)))
369 @receiver(post_delete)
370 def remove_soundfile(sender, instance=None, **kwargs):
371 from emissions.models import SoundFile
372 if not issubclass(sender, SoundFile):
374 ScheduledDiffusion.objects.filter(
375 diffusion__episode_id=instance.episode_id,
376 stream_id=None).update(
381 def save_soundfile(sender, instance=None, **kwargs):
382 if not app_settings.AUTO_SCHEDULE:
384 from emissions.models import SoundFile
385 if not issubclass(sender, SoundFile):
387 if instance.fragment:
389 if not instance.duration:
391 for i, diffusion in enumerate(instance.episode.diffusion_set.all()):
393 jingle_qs = Jingle.objects.filter(default_for_initial_diffusions=True)
395 jingle_qs = Jingle.objects.filter(default_for_reruns=True)
396 ScheduledDiffusion.objects.get_or_create(
397 diffusion=diffusion, stream_id=None,
398 defaults={'jingle': jingle_qs.order_by('?').first()})
402 def save_diffusion(sender, instance=None, **kwargs):
403 if not app_settings.AUTO_SCHEDULE:
405 from emissions.models import Diffusion
406 if not issubclass(sender, Diffusion):
408 if instance.episode.soundfile_set.filter(fragment=False).exists():
409 if Diffusion.objects.filter(episode=instance.episode, datetime__lt=instance.datetime).exists():
411 jingle_qs = Jingle.objects.filter(default_for_reruns=True)
413 jingle_qs = Jingle.objects.filter(default_for_initial_diffusions=True)
414 ScheduledDiffusion.objects.get_or_create(diffusion=instance,
415 stream_id=None, defaults={'jingle': jingle_qs.order_by('?').first()})