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