]> git.0d.be Git - django-panik-nonstop.git/blob - nonstop/models.py
75631049ab1dd184bf4b3bd4088b100f1f1aa34a
[django-panik-nonstop.git] / nonstop / models.py
1 import datetime
2 import os
3 import random
4
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 _
12
13 from .app_settings import app_settings
14
15
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',
26 }
27
28
29
30 class Artist(models.Model):
31     name = models.CharField(_('Name'), max_length=255)
32
33     class Meta:
34         ordering = ['name']
35
36     def __str__(self):
37         return self.name
38
39     def get_absolute_url(self):
40         return reverse('artist-view', kwargs={'pk': self.id})
41
42     def recent_diffusions(self):
43         return SomaLogLine.objects.filter(filepath__track__artist=self
44                 ).exclude(on_air=False).order_by('-play_timestamp')
45
46
47 class Album(models.Model):
48     name = models.CharField(_('Name'), max_length=255)
49
50
51 LANGUAGES = [
52     ('en', _('English')),
53     ('fr', _('French')),
54     ('nl', _('Dutch')),
55     ('other', _('Other')),
56     ('na', _('Not applicable')),
57 ]
58
59 class Track(models.Model):
60     title = models.CharField(_('Title'), max_length=255)
61     artist = models.ForeignKey(Artist, null=True)
62     album = models.ForeignKey(Album, null=True)
63     instru = models.BooleanField(_('Instru'), default=False)
64     language = models.CharField(max_length=10,
65             choices=LANGUAGES, blank=True)
66     sabam = models.BooleanField('SABAM', default=True)
67     cfwb = models.BooleanField('CFWB', default=False)
68     nonstop_zones = models.ManyToManyField('emissions.Nonstop', blank=True)
69
70     creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
71     added_to_nonstop_timestamp = models.DateTimeField(null=True)
72     uploader = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
73     duration = models.DurationField(_('Duration'), null=True)
74
75     class Meta:
76         ordering = ['creation_timestamp']
77
78     def __str__(self):
79         return 'Track %s (%s)' % (self.title, self.artist or 'unknown')
80
81     def get_absolute_url(self):
82         return reverse('track-view', kwargs={'pk': self.id})
83
84     def recent_diffusions(self):
85         return SomaLogLine.objects.filter(filepath__track=self
86                 ).exclude(on_air=False).order_by('-play_timestamp')
87
88     def file_path(self):
89         nfile = None
90         for nfile in self.nonstopfile_set.all().order_by('creation_timestamp'):
91             if os.path.exists(nfile.get_local_filepath()):
92                 return nfile.get_local_filepath()
93         if nfile:
94            return nfile.get_local_filepath()
95         return None
96
97     def file_exists(self):
98         file_path = self.file_path()
99         if not file_path:
100             return False
101         try:
102             return os.path.exists(file_path)
103         except AttributeError:
104             return False
105
106     def sync_nonstop_zones(self):
107         current_zones = self.nonstop_zones.all()
108         if current_zones.count():
109             if not self.added_to_nonstop_timestamp:
110                 self.added_to_nonstop_timestamp = now()
111                 self.save()
112         else:
113             self.added_to_nonstop_timestamp = None
114             self.save()
115
116         if not self.file_exists():
117             return
118         nonstop_file = self.nonstopfile_set.order_by('creation_timestamp').last()
119         filename = nonstop_file.filename
120         from emissions.models import Nonstop
121
122         for zone in Nonstop.objects.all():
123             if not zone.slug in TRANCHE_SLUG_DIR_MAPPING:
124                 continue
125             zone_dir = TRANCHE_SLUG_DIR_MAPPING[zone.slug]
126             zone_path = os.path.join(app_settings.LOCAL_BASE_PATH, 'Tranches', zone_dir, filename)
127             if zone in current_zones:
128                 if not os.path.exists(zone_path):
129                     os.symlink(os.path.join('..', '..', nonstop_file.short), zone_path)
130             else:
131                 if os.path.exists(zone_path):
132                     os.unlink(zone_path)
133
134
135 class NonstopFile(models.Model):
136     filepath = models.CharField(_('Filepath'), max_length=255)
137     filename = models.CharField(_('Filename'), max_length=255, null=True)
138     creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
139     track = models.ForeignKey(Track, null=True)
140
141     @property
142     def short(self):
143         return self.filepath[len(app_settings.REMOTE_BASE_PATH):]
144
145     def set_track_filepath(self, filepath):
146         self.filepath = os.path.join(app_settings.REMOTE_BASE_PATH, 'tracks', filepath)
147         self.filename = os.path.basename(filepath)
148
149     def get_local_filepath(self):
150         if not self.short:
151             return None
152         return os.path.join(app_settings.LOCAL_BASE_PATH, self.short)
153
154
155 class SomaLogLine(models.Model):
156     class Meta:
157         verbose_name = _('Soma log line')
158         verbose_name_plural = _('Soma log lines')
159         ordering = ['play_timestamp']
160
161     filepath = models.ForeignKey(NonstopFile, null=True)
162     track = models.ForeignKey(Track, null=True)
163     play_timestamp = models.DateTimeField()
164     on_air = models.NullBooleanField('On Air')
165
166     def get_track(self):
167         if self.track_id:
168             return self.track
169         if self.filepath_id:
170             return self.filepath.track
171         return None
172
173
174 class Jingle(models.Model):
175     class Meta:
176         verbose_name = _('Jingle')
177         verbose_name_plural = _('Jingles')
178         ordering = ['label']
179
180     label = models.CharField(_('Label'), max_length=100)
181     filepath = models.CharField(_('File Path'), max_length=255)
182     duration = models.DurationField(_('Duration'), null=True, blank=True)
183     default_for_initial_diffusions = models.BooleanField(_('Default for initial diffusions'), default=False)
184     default_for_reruns = models.BooleanField(_('Default for reruns'), default=False)
185     default_for_streams = models.BooleanField(_('Default for streams'), default=False)
186
187     def __str__(self):
188         return self.label
189
190     @property
191     def short(self):
192         # property for compatibility with Track model
193         # for jingles self.filepath is actually only the last part of the path,
194         # ex: jingles panik/H_marimba_RP_chucho_zoe.wav
195         return self.filepath
196
197     def file_path(self):
198         # for compatibility with Track model method
199         return self.get_local_filepath()
200
201     def get_local_filepath(self):
202         if not self.short:
203             return None
204         return os.path.join(app_settings.LOCAL_BASE_PATH, app_settings.JINGLES_PREFIX, self.short)
205
206     @property
207     def title(self):
208         return self.label
209
210
211 class Stream(models.Model):
212     class Meta:
213         verbose_name = _('Stream')
214         verbose_name_plural = _('Streams')
215         ordering = ['label']
216
217     label = models.CharField(_('Label'), max_length=100)
218     url = models.URLField(_('URL'), max_length=255)
219
220     def __str__(self):
221         return self.label
222
223
224 class ScheduledDiffusion(models.Model):
225     class Meta:
226         verbose_name = _('Scheduled diffusion')
227         verbose_name_plural = _('Scheduled diffusions')
228
229     diffusion = models.ForeignKey('emissions.Diffusion', null=True, blank=True, on_delete=models.SET_NULL)
230     jingle = models.ForeignKey(Jingle, null=True, blank=True)
231     stream = models.ForeignKey(Stream, null=True, blank=True)
232     creation_timestamp = models.DateTimeField(auto_now_add=True, null=True)
233     added_to_nonstop_timestamp = models.DateTimeField(null=True)
234     auto_delayed = models.BooleanField(default=False)
235
236     def __str__(self):
237         return 'Diffusion of %s' % self.diffusion
238
239     @property
240     def datetime(self):
241         return self.diffusion.datetime
242
243     @property
244     def end_datetime(self):
245         if self.is_stream():
246             return self.diffusion.end_datetime
247         dt = self.diffusion.datetime
248         if self.jingle and self.jingle.duration:
249             dt += self.jingle.duration
250         dt += datetime.timedelta(seconds=self.soundfile.duration)
251         return dt
252
253     @property
254     def soundfile(self):
255         return self.diffusion.episode.soundfile_set.filter(fragment=False).first()
256
257     @property
258     def duration(self):
259         return (self.end_datetime - self.datetime).seconds
260
261     @property
262     def episode(self):
263         return self.diffusion.episode
264
265     @property
266     def soma_id(self):
267         if self.is_stream():
268             return '[stream:%s]' % self.id
269         else:
270             return '[sound:%s]' % self.id
271
272     def is_stream(self):
273         return bool(self.stream_id)
274
275     def get_jingle_filepath(self):
276         return self.jingle.get_local_filepath() if self.jingle_id else None
277
278     def file_path(self):
279         # TODO, copy and stuff
280         return self.soundfile.file.path
281
282
283 class NonstopZoneSettings(models.Model):
284     nonstop = models.ForeignKey('emissions.Nonstop', on_delete=models.CASCADE)
285     intro_jingle = models.ForeignKey(Jingle, blank=True, null=True, related_name='+')
286     jingles = models.ManyToManyField(Jingle, blank=True)
287
288     def __str__(self):
289         return str(self.nonstop)
290
291
292 class RecurringStreamDiffusion(models.Model):
293     schedule = models.ForeignKey('emissions.Schedule', on_delete=models.CASCADE)
294     jingle = models.ForeignKey(Jingle, null=True, blank=True)
295     stream = models.ForeignKey(Stream)
296     is_active = models.BooleanField('Active', default=True)
297
298     def __str__(self):
299         return _('Recurring Stream for %s') % self.schedule
300
301
302 class RecurringOccurenceMixin:
303
304     @property
305     def jingle(self):
306         return self.diffusion.jingle
307
308     @property
309     def jingle_id(self):
310         return self.diffusion.jingle_id
311
312     @property
313     def duration(self):
314         return self.diffusion.schedule.get_duration() * 60
315
316     @property
317     def end_datetime(self):
318         return self.datetime + datetime.timedelta(minutes=self.diffusion.schedule.get_duration())
319
320     def get_jingle_filepath(self):
321         return self.diffusion.jingle.get_local_filepath() if self.diffusion.jingle_id else None
322
323
324 class RecurringStreamOccurence(models.Model, RecurringOccurenceMixin):
325     diffusion = models.ForeignKey(RecurringStreamDiffusion, on_delete=models.CASCADE)
326     datetime = models.DateTimeField(_('Date/time'), db_index=True)
327
328     def __str__(self):
329         return 'Recurring stream of %s' % self.diffusion.stream
330
331     @property
332     def stream(self):
333         return self.diffusion.stream
334
335     def is_stream(self):
336         return True
337
338
339 class RecurringRandomDirectoryDiffusion(models.Model):
340     # between soundfiles and nonstop zones, this is used for the "mix
341     # deliveries" segment during weekend nights.
342     schedule = models.ForeignKey('emissions.Schedule', on_delete=models.CASCADE)
343     jingle = models.ForeignKey(Jingle, null=True, blank=True)
344     directory = models.CharField(_('Directory'), max_length=255)
345     is_active = models.BooleanField('Active', default=True)
346
347     def __str__(self):
348         return _('Recurring Random Directory for %s') % self.schedule
349
350
351 class RecurringRandomDirectoryOccurence(models.Model, RecurringOccurenceMixin):
352     diffusion = models.ForeignKey(RecurringRandomDirectoryDiffusion, on_delete=models.CASCADE)
353     datetime = models.DateTimeField(_('Date/time'), db_index=True)
354
355     def is_stream(self):
356         return False
357
358     def file_path(self):
359         directory = self.diffusion.directory
360         return os.path.join(directory, random.choice(os.listdir(directory)))
361
362
363 @receiver(post_delete)
364 def remove_soundfile(sender, instance=None, **kwargs):
365     from emissions.models import SoundFile
366     if not issubclass(sender, SoundFile):
367         return
368     ScheduledDiffusion.objects.filter(
369             diffusion__episode_id=instance.episode_id,
370             stream_id=None).update(
371             diffusion=None)
372
373
374 @receiver(post_save)
375 def save_soundfile(sender, instance=None, **kwargs):
376     if not app_settings.AUTO_SCHEDULE:
377         return
378     from emissions.models import SoundFile
379     if not issubclass(sender, SoundFile):
380         return
381     if instance.fragment:
382         return
383     if not instance.duration:
384         return
385     for i, diffusion in enumerate(instance.episode.diffusion_set.all()):
386         if i == 0:
387             jingle_qs = Jingle.objects.filter(default_for_initial_diffusions=True)
388         else:
389             jingle_qs = Jingle.objects.filter(default_for_reruns=True)
390         ScheduledDiffusion.objects.get_or_create(
391                 diffusion=diffusion, stream_id=None,
392                 defaults={'jingle': jingle_qs.order_by('?').first()})
393
394
395 @receiver(post_save)
396 def save_diffusion(sender, instance=None, **kwargs):
397     if not app_settings.AUTO_SCHEDULE:
398         return
399     from emissions.models import Diffusion
400     if not issubclass(sender, Diffusion):
401         return
402     if instance.episode.soundfile_set.filter(fragment=False).exists():
403         if Diffusion.objects.filter(episode=instance.episode, datetime__lt=instance.datetime).exists():
404             # rerun
405             jingle_qs = Jingle.objects.filter(default_for_reruns=True)
406         else:
407             jingle_qs = Jingle.objects.filter(default_for_initial_diffusions=True)
408         ScheduledDiffusion.objects.get_or_create(diffusion=instance,
409                 stream_id=None, defaults={'jingle': jingle_qs.order_by('?').first()})