forked from rc/aircox
Merge branch 'streamer_live'
This commit is contained in:
commit
013a0894ab
|
@ -183,8 +183,8 @@ class StationAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(Log)
|
@admin.register(Log)
|
||||||
class LogAdmin(admin.ModelAdmin):
|
class LogAdmin(admin.ModelAdmin):
|
||||||
list_display = ['id', 'date', 'station', 'source', 'type', 'comment', 'related']
|
list_display = ['id', 'date', 'station', 'source', 'type', 'comment', 'diffusion', 'sound', 'track']
|
||||||
list_filter = ['date', 'source', 'related_type']
|
list_filter = ['date', 'source', 'diffusion', 'sound', 'track']
|
||||||
|
|
||||||
admin.site.register(Port)
|
admin.site.register(Port)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,14 @@ from django.conf import settings as main_settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
|
|
||||||
from aircox.models import Station, Diffusion, Track, Sound, Log
|
from aircox.models import Station, Diffusion, Track, Sound, Log #, DiffusionLog, SoundLog
|
||||||
|
|
||||||
|
|
||||||
|
class Tracer:
|
||||||
|
"""
|
||||||
|
Keep trace of played item and update logs in adequation to it
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Monitor:
|
class Monitor:
|
||||||
|
@ -65,11 +72,12 @@ class Monitor:
|
||||||
self.sync_playlists()
|
self.sync_playlists()
|
||||||
self.handle()
|
self.handle()
|
||||||
|
|
||||||
def log(self, **kwargs):
|
def log(self, date = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a log using **kwargs, and print info
|
Create a log using **kwargs, and print info
|
||||||
"""
|
"""
|
||||||
log = Log(station = self.station, **kwargs)
|
log = Log(station = self.station, date = date or tz.now(),
|
||||||
|
**kwargs)
|
||||||
log.save()
|
log.save()
|
||||||
log.print()
|
log.print()
|
||||||
|
|
||||||
|
@ -84,17 +92,18 @@ class Monitor:
|
||||||
if not current_sound or not current_source:
|
if not current_sound or not current_source:
|
||||||
return
|
return
|
||||||
|
|
||||||
log = Log.objects.get_for(self.station, model = Sound) \
|
log = Log.objects.station(self.station, sound__isnull = False) \
|
||||||
|
.select_related('sound') \
|
||||||
.order_by('date').last()
|
.order_by('date').last()
|
||||||
|
|
||||||
# only streamed
|
# only streamed ns
|
||||||
if log and (log.related and not log.related.diffusion):
|
if log and not log.sound.diffusion:
|
||||||
self.trace_sound_tracks(log)
|
self.trace_sound_tracks(log)
|
||||||
|
|
||||||
# TODO: expiration
|
# TODO: expiration
|
||||||
if log and (log.source == current_source.id and \
|
if log and (log.source == current_source.id and \
|
||||||
log.related and
|
log.sound and
|
||||||
log.related.path == current_sound):
|
log.sound.path == current_sound):
|
||||||
return
|
return
|
||||||
|
|
||||||
sound = Sound.objects.filter(path = current_sound)
|
sound = Sound.objects.filter(path = current_sound)
|
||||||
|
@ -102,7 +111,7 @@ class Monitor:
|
||||||
type = Log.Type.play,
|
type = Log.Type.play,
|
||||||
source = current_source.id,
|
source = current_source.id,
|
||||||
date = tz.now(),
|
date = tz.now(),
|
||||||
related = sound[0] if sound else None,
|
sound = sound[0] if sound else None,
|
||||||
# keep sound path (if sound is removed, we keep that info)
|
# keep sound path (if sound is removed, we keep that info)
|
||||||
comment = current_sound,
|
comment = current_sound,
|
||||||
)
|
)
|
||||||
|
@ -112,11 +121,12 @@ class Monitor:
|
||||||
Log tracks for the given sound (for streamed programs); Called by
|
Log tracks for the given sound (for streamed programs); Called by
|
||||||
self.trace
|
self.trace
|
||||||
"""
|
"""
|
||||||
logs = Log.objects.get_for(self.station, model = Track) \
|
logs = Log.objects.station(self.station,
|
||||||
.filter(pk__gt = log.pk)
|
track__isnull = False,
|
||||||
logs = [ log.related_id for log in logs ]
|
pk__gt = log.pk) \
|
||||||
|
.values_list('sound__pk', flat = True)
|
||||||
|
|
||||||
tracks = Track.objects.get_for(object = log.related) \
|
tracks = Track.objects.get_for(object = log.sound) \
|
||||||
.filter(in_seconds = True)
|
.filter(in_seconds = True)
|
||||||
if tracks and len(tracks) == len(logs):
|
if tracks and len(tracks) == len(logs):
|
||||||
return
|
return
|
||||||
|
@ -130,7 +140,7 @@ class Monitor:
|
||||||
type = Log.Type.play,
|
type = Log.Type.play,
|
||||||
source = log.source,
|
source = log.source,
|
||||||
date = pos,
|
date = pos,
|
||||||
related = track,
|
track = track,
|
||||||
comment = track,
|
comment = track,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -147,9 +157,9 @@ class Monitor:
|
||||||
for source in self.station.sources:
|
for source in self.station.sources:
|
||||||
if source == self.station.dealer:
|
if source == self.station.dealer:
|
||||||
continue
|
continue
|
||||||
playlist = [ sound.path for sound in
|
playlist = source.program.sound_set.all() \
|
||||||
source.program.sound_set.all() ]
|
.values_list('path', flat = True)
|
||||||
source.playlist = playlist
|
source.playlist = list(playlist)
|
||||||
|
|
||||||
def trace_canceled(self):
|
def trace_canceled(self):
|
||||||
"""
|
"""
|
||||||
|
@ -163,18 +173,18 @@ class Monitor:
|
||||||
type = Diffusion.Type.normal,
|
type = Diffusion.Type.normal,
|
||||||
sound__type = Sound.Type.archive,
|
sound__type = Sound.Type.archive,
|
||||||
)
|
)
|
||||||
logs = station.played(models = Diffusion)
|
logs = station.played(diffusion__isnull = False)
|
||||||
|
|
||||||
date = tz.now() - datetime.timedelta(seconds = self.cancel_timeout)
|
date = tz.now() - datetime.timedelta(seconds = self.cancel_timeout)
|
||||||
for diff in diffs:
|
for diff in diffs:
|
||||||
if logs.filter(related = diff):
|
if logs.filter(diffusion = diff):
|
||||||
continue
|
continue
|
||||||
if diff.start < now:
|
if diff.start < now:
|
||||||
diff.type = Diffusion.Type.canceled
|
diff.type = Diffusion.Type.canceled
|
||||||
diff.save()
|
diff.save()
|
||||||
self.log(
|
self.log(
|
||||||
type = Log.Type.other,
|
type = Log.Type.other,
|
||||||
related = diff,
|
diffusion = diff,
|
||||||
comment = 'Diffusion canceled after {} seconds' \
|
comment = 'Diffusion canceled after {} seconds' \
|
||||||
.format(self.cancel_timeout)
|
.format(self.cancel_timeout)
|
||||||
)
|
)
|
||||||
|
@ -187,53 +197,81 @@ class Monitor:
|
||||||
station = self.station
|
station = self.station
|
||||||
now = tz.now()
|
now = tz.now()
|
||||||
|
|
||||||
diff_log = station.played(models = Diffusion) \
|
log = station.played(diffusion__isnull = False) \
|
||||||
.order_by('date').last()
|
.select_related('diffusion') \
|
||||||
if not diff_log or \
|
.order_by('date').last()
|
||||||
not diff_log.related.is_date_in_range(now):
|
if not log or not log.diffusion.is_date_in_range(now):
|
||||||
|
# not running anymore
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
# sound has switched? assume it has been (forced to) stopped
|
# last sound source change: end of file reached or forced to stop
|
||||||
sounds = station.played(models = Sound) \
|
sounds = station.played(sound__isnull = False) \
|
||||||
.filter(date__gte = diff_log.date) \
|
.filter(date__gte = log.date) \
|
||||||
.order_by('date')
|
.order_by('date')
|
||||||
|
|
||||||
if sounds.last() and sounds.last().source != diff_log.source:
|
if sounds.count() and sounds.last().source != log.source:
|
||||||
return diff_log, []
|
return None, []
|
||||||
|
|
||||||
# last diff is still playing: get the remaining playlist
|
# last diff is still playing: get remaining playlist
|
||||||
sounds = sounds.filter(
|
sounds = sounds \
|
||||||
source = diff_log.source, pk__gt = diff_log.pk
|
.filter(source = log.source, pk__gt = log.pk) \
|
||||||
)
|
.exclude(sound__type = Sound.Type.removed)
|
||||||
sounds = [
|
|
||||||
sound.related.path for sound in sounds
|
|
||||||
if sound.related.type != Sound.Type.removed
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
remaining = log.diffusion.get_archives().exclude(pk__in = sounds) \
|
||||||
diff_log.related,
|
.values_list('path', flat = True)
|
||||||
[ path for path in diff_log.related.playlist
|
return log.diffusion, list(remaining)
|
||||||
if path not in sounds ]
|
|
||||||
)
|
|
||||||
|
|
||||||
def __next_diff(self, diff):
|
def __next_diff(self, diff):
|
||||||
"""
|
"""
|
||||||
Return the tuple with the next diff that should be played and
|
Return the next diffusion to be played as tuple of (diff, playlist).
|
||||||
the playlist
|
If diff is given, it is the one to be played right after it.
|
||||||
|
|
||||||
Note: diff is a log
|
|
||||||
"""
|
"""
|
||||||
station = self.station
|
station = self.station
|
||||||
now = tz.now()
|
now = tz.now()
|
||||||
|
|
||||||
args = {'start__gt': diff.date } if diff else {}
|
kwargs = {'start__gte': diff.end } if diff else {}
|
||||||
diff = Diffusion.objects.at(station, now).filter(
|
diff = Diffusion.objects \
|
||||||
type = Diffusion.Type.normal,
|
.at(station, now) \
|
||||||
sound__type = Sound.Type.archive,
|
.filter(type = Diffusion.Type.normal, **kwargs) \
|
||||||
**args
|
.distinct().order_by('start')
|
||||||
).distinct().order_by('start').first()
|
diff = diff.first()
|
||||||
return (diff, diff and diff.playlist or [])
|
return (diff, diff and diff.playlist or [])
|
||||||
|
|
||||||
|
def handle_pl_sync(self, source, playlist, diff = None, date = None):
|
||||||
|
"""
|
||||||
|
Update playlist of a source if required, and handle logging when
|
||||||
|
it is needed.
|
||||||
|
"""
|
||||||
|
dealer = self.station.dealer
|
||||||
|
if dealer.playlist != playlist:
|
||||||
|
dealer.playlist = playlist
|
||||||
|
if diff and not diff.is_live():
|
||||||
|
self.log(type = Log.Type.load, source = source.id,
|
||||||
|
diffusion = diff, date = date)
|
||||||
|
|
||||||
|
def handle_diff_start(self, source, diff, date):
|
||||||
|
"""
|
||||||
|
Enable dealer in order to play a given diffusion if required,
|
||||||
|
handle start of diffusion
|
||||||
|
"""
|
||||||
|
if not diff or diff.start > date:
|
||||||
|
return
|
||||||
|
|
||||||
|
# live: just log it
|
||||||
|
if diff.is_live():
|
||||||
|
diff_ = Log.objects.station(self.station) \
|
||||||
|
.filter(diffusion = diff)
|
||||||
|
if not diff_.count():
|
||||||
|
self.log(type = Log.Type.on_air, source = source.id,
|
||||||
|
diffusion = diff, date = date)
|
||||||
|
return
|
||||||
|
|
||||||
|
# enable dealer
|
||||||
|
if not dealer.active:
|
||||||
|
dealer.active = True
|
||||||
|
self.log(type = Log.Type.play, source = source.id,
|
||||||
|
diffusion = diff, date = date)
|
||||||
|
|
||||||
def handle(self):
|
def handle(self):
|
||||||
"""
|
"""
|
||||||
Handle scheduled diffusion, trigger if needed, preload playlists
|
Handle scheduled diffusion, trigger if needed, preload playlists
|
||||||
|
@ -246,33 +284,15 @@ class Monitor:
|
||||||
now = tz.now()
|
now = tz.now()
|
||||||
|
|
||||||
# current and next diffs
|
# current and next diffs
|
||||||
diff, playlist = self.__current_diff()
|
current_diff, remaining_pl = self.__current_diff()
|
||||||
dealer.active = bool(playlist)
|
next_diff, next_pl = self.__next_diff(current_diff)
|
||||||
|
|
||||||
next_diff, next_playlist = self.__next_diff(diff)
|
# playlist
|
||||||
playlist += next_playlist
|
dealer.active = bool(remaining_pl)
|
||||||
|
playlist = remaining_pl + next_pl
|
||||||
|
|
||||||
# playlist update
|
self.handle_pl_sync(dealer, playlist, next_diff, now)
|
||||||
if dealer.playlist != playlist:
|
self.handle_diff_start(dealer, next_diff, now)
|
||||||
dealer.playlist = playlist
|
|
||||||
if next_diff:
|
|
||||||
self.log(
|
|
||||||
type = Log.Type.load,
|
|
||||||
source = dealer.id,
|
|
||||||
date = now,
|
|
||||||
related = next_diff
|
|
||||||
)
|
|
||||||
|
|
||||||
# dealer.on when next_diff start <= now
|
|
||||||
if next_diff and not dealer.active and \
|
|
||||||
next_diff.start <= now:
|
|
||||||
dealer.active = True
|
|
||||||
self.log(
|
|
||||||
type = Log.Type.play,
|
|
||||||
source = dealer.id,
|
|
||||||
date = now,
|
|
||||||
related = next_diff,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Command (BaseCommand):
|
class Command (BaseCommand):
|
||||||
|
|
231
aircox/models.py
231
aircox/models.py
|
@ -50,7 +50,6 @@ class RelatedManager(models.Manager):
|
||||||
qs = qs.filter(related_id = object.pk)
|
qs = qs.filter(related_id = object.pk)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class Related(models.Model):
|
class Related(models.Model):
|
||||||
"""
|
"""
|
||||||
Add a field "related" of type GenericForeignKey, plus utilities.
|
Add a field "related" of type GenericForeignKey, plus utilities.
|
||||||
|
@ -148,7 +147,6 @@ class Track(Related):
|
||||||
verbose_name = _('Track')
|
verbose_name = _('Track')
|
||||||
verbose_name_plural = _('Tracks')
|
verbose_name_plural = _('Tracks')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Station related classes
|
# Station related classes
|
||||||
#
|
#
|
||||||
|
@ -180,7 +178,7 @@ class Station(Nameable):
|
||||||
__dealer = None
|
__dealer = None
|
||||||
__streamer = None
|
__streamer = None
|
||||||
|
|
||||||
def __prepare(self):
|
def __prepare_controls(self):
|
||||||
import aircox.controllers as controllers
|
import aircox.controllers as controllers
|
||||||
if not self.__streamer:
|
if not self.__streamer:
|
||||||
self.__streamer = controllers.Streamer(station = self)
|
self.__streamer = controllers.Streamer(station = self)
|
||||||
|
@ -215,12 +213,12 @@ class Station(Nameable):
|
||||||
"""
|
"""
|
||||||
Audio sources, dealer included
|
Audio sources, dealer included
|
||||||
"""
|
"""
|
||||||
self.__prepare()
|
self.__prepare_controls()
|
||||||
return self.__sources
|
return self.__sources
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dealer(self):
|
def dealer(self):
|
||||||
self.__prepare()
|
self.__prepare_controls()
|
||||||
return self.__dealer
|
return self.__dealer
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -228,86 +226,25 @@ class Station(Nameable):
|
||||||
"""
|
"""
|
||||||
Audio controller for the station
|
Audio controller for the station
|
||||||
"""
|
"""
|
||||||
self.__prepare()
|
self.__prepare_controls()
|
||||||
return self.__streamer
|
return self.__streamer
|
||||||
|
|
||||||
def played(self, models, archives = True):
|
def played(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Call Log.objects.played for this station
|
Call Log.objects.played for this station
|
||||||
"""
|
"""
|
||||||
return Log.objects.played(self, models, archives)
|
return Log.objects.played(self, *args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __mix_logs_and_diff(diffs, logs, count = 0):
|
|
||||||
"""
|
|
||||||
Mix together logs and diffusion items of the same day,
|
|
||||||
ordered by their date.
|
|
||||||
|
|
||||||
Diffs and Logs are assumed to be ordered by -date, and so is
|
|
||||||
the resulting list
|
|
||||||
"""
|
|
||||||
# we fill a list with diff and retrieve logs that happened between
|
|
||||||
# each to put them too there.
|
|
||||||
# we do the algorithm in the reverse way in order to be able to limit
|
|
||||||
# process calculations using count if needed.
|
|
||||||
diff_ = None
|
|
||||||
now = tz.now()
|
|
||||||
items = []
|
|
||||||
|
|
||||||
logs = logs.order_by('-date')
|
|
||||||
for diff in diffs.order_by('-start'):
|
|
||||||
if diff_:
|
|
||||||
logs_ = logs.filter(date__gt = diff.end, date__lt = diff_.start)
|
|
||||||
else:
|
|
||||||
logs_ = logs.filter(date__gt = diff.end)
|
|
||||||
|
|
||||||
if diff.end < now:
|
|
||||||
# a log can be started before the end of the diffusion and still
|
|
||||||
# is running. We can't say if it has been properly finished
|
|
||||||
# before the end of the diffusion, but we assume that in most
|
|
||||||
# cases this is true.
|
|
||||||
# We just check if there is some other log after this partial
|
|
||||||
# one.
|
|
||||||
partial = logs.filter(
|
|
||||||
date__gt = diff.start, date__lt = diff.end
|
|
||||||
).last()
|
|
||||||
if partial:
|
|
||||||
next_log = logs.filter(pk__gt = partial.pk).first()
|
|
||||||
if not next_log or next_log.date > diff.end:
|
|
||||||
partial.date = diff.end
|
|
||||||
logs_ = list(logs_) + [partial]
|
|
||||||
|
|
||||||
# append to list
|
|
||||||
diff_ = diff
|
|
||||||
items.extend(logs_)
|
|
||||||
items.append(diff)
|
|
||||||
if count and len(items) >= count:
|
|
||||||
break
|
|
||||||
|
|
||||||
if diff_:
|
|
||||||
if count and len(items) >= count:
|
|
||||||
return items[:count]
|
|
||||||
logs_ = logs.filter(date__lt = diff_.start)
|
|
||||||
else:
|
|
||||||
logs_ = logs.all()
|
|
||||||
|
|
||||||
items.extend(logs_)
|
|
||||||
return items[:count] if count else items
|
|
||||||
|
|
||||||
def on_air(self, date = None, count = 0):
|
def on_air(self, date = None, count = 0):
|
||||||
"""
|
"""
|
||||||
Return a list of what happened on air, based on logs and
|
Return a queryset of what happened on air, based on logs and
|
||||||
diffusions informations. The list is sorted by -date.
|
diffusions informations. The queryset is sorted by -date.
|
||||||
|
|
||||||
* date: only for what happened on this date;
|
* date: only for what happened on this date;
|
||||||
* count: number of items to retrieve if not zero;
|
* count: number of items to retrieve if not zero;
|
||||||
|
|
||||||
If date is not specified, count MUST be set to a non-zero value.
|
If date is not specified, count MUST be set to a non-zero value.
|
||||||
Be careful with what you which for: the result is a plain list.
|
Be careful with what you which for: the result is a plain list.
|
||||||
|
|
||||||
The list contains:
|
|
||||||
* track logs: for the streamed programs;
|
|
||||||
* diffusion: for the scheduled diffusions;
|
|
||||||
"""
|
"""
|
||||||
# FIXME: as an iterator?
|
# FIXME: as an iterator?
|
||||||
# TODO argument to get sound instead of tracks
|
# TODO argument to get sound instead of tracks
|
||||||
|
@ -315,21 +252,39 @@ class Station(Nameable):
|
||||||
raise ValueError('at least one argument must be set')
|
raise ValueError('at least one argument must be set')
|
||||||
|
|
||||||
# FIXME can be a potential source of bug
|
# FIXME can be a potential source of bug
|
||||||
|
if date:
|
||||||
|
date = utils.cast_date(date, to_datetime = False)
|
||||||
|
|
||||||
if date and date > datetime.date.today():
|
if date and date > datetime.date.today():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if date:
|
if date:
|
||||||
logs = Log.objects.at_for(self, date, model = Track)
|
logs = Log.objects.at(self, date)
|
||||||
diffs = Diffusion.objects.at(self, date)
|
diffs = Diffusion.objects.at(self, date, type = Diffusion.Type.normal) \
|
||||||
|
.order_by('-start')
|
||||||
else:
|
else:
|
||||||
logs = Log.objects.get_for(self, model = Track)
|
logs = Log.objects
|
||||||
diffs = Diffusion.objects
|
diffs = Diffusion.objects.filter(type = Diffusion.Type.normal,
|
||||||
logs = logs.filter(station = self)
|
start__lte = tz.now()) \
|
||||||
|
.order_by('-start')[:count]
|
||||||
|
|
||||||
diffs = diffs.filter(program__station = self) \
|
q = models.Q(diffusion__isnull = False) | \
|
||||||
.filter(type = Diffusion.Type.normal) \
|
models.Q(track__isnull = False)
|
||||||
.filter(start__lte = tz.now())
|
logs = logs.filter(q).order_by('-date')
|
||||||
return self.__mix_logs_and_diff(diffs, logs, count)
|
|
||||||
|
# filter out tracks played when there was a diffusion
|
||||||
|
n = 0
|
||||||
|
q = models.Q()
|
||||||
|
for diff in diffs:
|
||||||
|
if count and n >= count:
|
||||||
|
break
|
||||||
|
q = q | models.Q(date__gte = diff.start, date__lte = diff.end)
|
||||||
|
n += 1
|
||||||
|
logs = logs.exclude(q, diffusion__isnull = True)
|
||||||
|
|
||||||
|
if count:
|
||||||
|
logs = logs[:count]
|
||||||
|
return logs
|
||||||
|
|
||||||
def save(self, make_sources = True, *args, **kwargs):
|
def save(self, make_sources = True, *args, **kwargs):
|
||||||
if not self.path:
|
if not self.path:
|
||||||
|
@ -348,9 +303,9 @@ class Station(Nameable):
|
||||||
|
|
||||||
|
|
||||||
class ProgramManager(models.Manager):
|
class ProgramManager(models.Manager):
|
||||||
def station(self, station, qs = None):
|
def station(self, station, qs = None, **kwargs):
|
||||||
qs = self if qs is None else qs
|
qs = self if qs is None else qs
|
||||||
return qs.filter(station = station)
|
return qs.filter(station = station, **kwargs)
|
||||||
|
|
||||||
class Program(Nameable):
|
class Program(Nameable):
|
||||||
"""
|
"""
|
||||||
|
@ -730,11 +685,11 @@ class Schedule(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class DiffusionManager(models.Manager):
|
class DiffusionManager(models.Manager):
|
||||||
def station(self, station, qs = None):
|
def station(self, station, qs = None, **kwargs):
|
||||||
qs = self if qs is None else qs
|
qs = self if qs is None else qs
|
||||||
return qs.filter(program__station = station)
|
return qs.filter(program__station = station, **kwargs)
|
||||||
|
|
||||||
def at(self, station, date = None, next = False, qs = None):
|
def at(self, station, date = None, next = False, qs = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return diffusions occuring at the given date, ordered by +start
|
Return diffusions occuring at the given date, ordered by +start
|
||||||
|
|
||||||
|
@ -770,7 +725,7 @@ class DiffusionManager(models.Manager):
|
||||||
if next:
|
if next:
|
||||||
# include also diffusions of the next day
|
# include also diffusions of the next day
|
||||||
filters |= models.Q(start__gte = start)
|
filters |= models.Q(start__gte = start)
|
||||||
qs = qs.filter(filters)
|
qs = qs.filter(filters, **kwargs)
|
||||||
return self.station(station, qs).order_by('start').distinct()
|
return self.station(station, qs).order_by('start').distinct()
|
||||||
|
|
||||||
def after(self, station, date = None, qs = None):
|
def after(self, station, date = None, qs = None):
|
||||||
|
@ -867,7 +822,12 @@ class Diffusion(models.Model):
|
||||||
"""
|
"""
|
||||||
List of archives' path; uses get_archives
|
List of archives' path; uses get_archives
|
||||||
"""
|
"""
|
||||||
return [ sound.path for sound in self.get_archives() ]
|
playlist = self.get_archives().values_list('path', flat = True)
|
||||||
|
return list(playlist)
|
||||||
|
|
||||||
|
def is_live(self):
|
||||||
|
return self.type == self.Type.normal and \
|
||||||
|
not self.get_archives().count()
|
||||||
|
|
||||||
def get_archives(self):
|
def get_archives(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1229,59 +1189,51 @@ class Port (models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LogManager(RelatedManager):
|
class LogManager(models.Manager):
|
||||||
def station(self, station, qs = None):
|
def station(self, station, qs = None, **kwargs):
|
||||||
qs = self if qs is None else qs
|
qs = self if qs is None else qs
|
||||||
return qs.filter(station = station)
|
return qs.filter(station = station, **kwargs)
|
||||||
|
|
||||||
def get_for(self, station, *args, **kwargs):
|
def _at(self, date = None, qs = None, **kwargs):
|
||||||
qs = super().get_for(*args, **kwargs)
|
|
||||||
return self.station(station, qs) if station else qs
|
|
||||||
|
|
||||||
def _at(self, date = None, qs = None):
|
|
||||||
start, end = utils.date_range(date)
|
start, end = utils.date_range(date)
|
||||||
qs = self if qs is None else qs
|
qs = self if qs is None else qs
|
||||||
return qs.filter(date__gte = start,
|
return qs.filter(date__gte = start, date__lte = end, **kwargs)
|
||||||
date__lte = end)
|
|
||||||
|
|
||||||
def at(self, station = None, date = None, qs = None):
|
def at(self, station = None, date = None, qs = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return a queryset of logs that have the given date
|
Return a queryset of logs that have the given date
|
||||||
in their range.
|
in their range.
|
||||||
"""
|
"""
|
||||||
qs = self._at(date, qs)
|
qs = self._at(date, qs, **kwargs)
|
||||||
return self.station(station, qs) if station else qs
|
return self.station(station, qs) if station else qs
|
||||||
|
|
||||||
def at_for(self, station, date, object = None, model = None, qs = None):
|
def played(self, station, archives = True, include_live = True,
|
||||||
"""
|
**kwargs):
|
||||||
Return a queryset of logs that occured at the given date
|
|
||||||
for the given model or object.
|
|
||||||
"""
|
|
||||||
qs = self.get_for(station, object, model, qs)
|
|
||||||
return self._at(date, qs)
|
|
||||||
|
|
||||||
def played(self, station, models, archives = True):
|
|
||||||
"""
|
"""
|
||||||
Return a queryset of the played elements' log for the given
|
Return a queryset of the played elements' log for the given
|
||||||
station and model. This queryset is ordered by date ascending
|
station and model. This queryset is ordered by date ascending
|
||||||
|
|
||||||
* station: related station
|
* station: related station
|
||||||
* models: a model or a list of models
|
|
||||||
* archives: if false, exclude log of diffusion's archives from
|
* archives: if false, exclude log of diffusion's archives from
|
||||||
the queryset;
|
the queryset;
|
||||||
|
* include_live: include diffusion that have no archive
|
||||||
|
* kwargs: extra filter kwargs
|
||||||
"""
|
"""
|
||||||
qs = self.get_for(station, model = models) \
|
if include_live:
|
||||||
.filter(type = Log.Type.play)
|
qs = self.filter(type__in = (Log.Type.play, Log.Type.on_air),
|
||||||
|
**kwargs)
|
||||||
|
else:
|
||||||
|
qs = self.filter(type = Log.Type.play, **kwargs)
|
||||||
|
|
||||||
if not archives and station.dealer:
|
if not archives and station.dealer:
|
||||||
qs = qs.exclude(
|
qs = qs.exclude(
|
||||||
source = station.dealer.id,
|
source = station.dealer.id,
|
||||||
related_type = ContentType.objects.get_for_model(Sound)
|
sound__isnull = False
|
||||||
)
|
)
|
||||||
return qs.order_by('date')
|
return qs.order_by('date')
|
||||||
|
|
||||||
|
|
||||||
class Log(Related):
|
class Log(models.Model):
|
||||||
"""
|
"""
|
||||||
Log sounds and diffusions that are played on the station.
|
Log sounds and diffusions that are played on the station.
|
||||||
|
|
||||||
|
@ -1303,14 +1255,18 @@ class Log(Related):
|
||||||
"""
|
"""
|
||||||
Source starts to be preload related_object
|
Source starts to be preload related_object
|
||||||
"""
|
"""
|
||||||
other = 0x03
|
on_air = 0x03
|
||||||
|
"""
|
||||||
|
A diffusion occured, but in live (no sound played by Aircox)
|
||||||
|
"""
|
||||||
|
other = 0x04
|
||||||
"""
|
"""
|
||||||
Other log
|
Other log
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type = models.SmallIntegerField(
|
type = models.SmallIntegerField(
|
||||||
verbose_name = _('type'),
|
verbose_name = _('type'),
|
||||||
choices = [ (int(y), _(x)) for x,y in Type.__members__.items() ],
|
choices = [ (int(y), _(x.replace('_',' '))) for x,y in Type.__members__.items() ],
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
)
|
)
|
||||||
station = models.ForeignKey(
|
station = models.ForeignKey(
|
||||||
|
@ -1329,6 +1285,7 @@ class Log(Related):
|
||||||
date = models.DateTimeField(
|
date = models.DateTimeField(
|
||||||
_('date'),
|
_('date'),
|
||||||
default=tz.now,
|
default=tz.now,
|
||||||
|
db_index = True,
|
||||||
)
|
)
|
||||||
comment = models.CharField(
|
comment = models.CharField(
|
||||||
_('comment'),
|
_('comment'),
|
||||||
|
@ -1336,6 +1293,25 @@ class Log(Related):
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
diffusion = models.ForeignKey(
|
||||||
|
Diffusion,
|
||||||
|
verbose_name = _('Diffusion'),
|
||||||
|
blank = True, null = True,
|
||||||
|
db_index = True,
|
||||||
|
)
|
||||||
|
sound = models.ForeignKey(
|
||||||
|
Sound,
|
||||||
|
verbose_name = _('Sound'),
|
||||||
|
blank = True, null = True,
|
||||||
|
db_index = True,
|
||||||
|
)
|
||||||
|
track = models.ForeignKey(
|
||||||
|
Track,
|
||||||
|
verbose_name = _('Track'),
|
||||||
|
blank = True, null = True,
|
||||||
|
db_index = True,
|
||||||
|
)
|
||||||
|
|
||||||
objects = LogManager()
|
objects = LogManager()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1343,12 +1319,16 @@ class Log(Related):
|
||||||
"""
|
"""
|
||||||
Calculated end using self.related informations
|
Calculated end using self.related informations
|
||||||
"""
|
"""
|
||||||
if self.related_type == Diffusion:
|
if self.diffusion:
|
||||||
return self.related.end
|
return self.diffusion.end
|
||||||
if self.related_type == Sound:
|
if self.sound:
|
||||||
return self.date + to_timedelta(self.duration)
|
return self.date + to_timedelta(sound.duration)
|
||||||
return self.date
|
return self.date
|
||||||
|
|
||||||
|
@property
|
||||||
|
def related(self):
|
||||||
|
return self.diffusion or self.sound or self.track
|
||||||
|
|
||||||
def is_expired(self, date = None):
|
def is_expired(self, date = None):
|
||||||
"""
|
"""
|
||||||
Return True if the log is expired. Note that it only check
|
Return True if the log is expired. Note that it only check
|
||||||
|
@ -1359,11 +1339,19 @@ class Log(Related):
|
||||||
return self.end < date
|
return self.end < date
|
||||||
|
|
||||||
def print(self):
|
def print(self):
|
||||||
|
r = []
|
||||||
|
if self.diffusion:
|
||||||
|
r.append('diff: ' + str(self.diffusion_id))
|
||||||
|
if self.sound:
|
||||||
|
r.append('sound: ' + str(self.sound_id))
|
||||||
|
if self.track:
|
||||||
|
r.append('track: ' + str(self.track_id))
|
||||||
|
|
||||||
|
|
||||||
logger.info('log #%s: %s%s',
|
logger.info('log #%s: %s%s',
|
||||||
str(self),
|
str(self),
|
||||||
self.comment or '',
|
self.comment or '',
|
||||||
' -- {} #{}'.format(self.related_type, self.related_id)
|
' (' + ', '.join(r) + ')' if r else ''
|
||||||
if self.related else ''
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -1371,4 +1359,3 @@ class Log(Related):
|
||||||
self.pk, self.date.strftime('%Y/%m/%d %H:%M'), self.source
|
self.pk, self.date.strftime('%Y/%m/%d %H:%M'), self.source
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,12 +40,12 @@ def on_air(request):
|
||||||
else:
|
else:
|
||||||
station = stations.stations.first()
|
station = stations.stations.first()
|
||||||
|
|
||||||
last = station.on_air(count = 10)
|
on_air = station.on_air(count = 10).select_related('track','diffusion')
|
||||||
if not last:
|
if not on_air.count():
|
||||||
return HttpResponse('')
|
return HttpResponse('')
|
||||||
|
|
||||||
last = last[0]
|
last = on_air.last()
|
||||||
if type(last) == models.Log:
|
if last.track:
|
||||||
last = {
|
last = {
|
||||||
'type': 'track',
|
'type': 'track',
|
||||||
'artist': last.related.artist,
|
'artist': last.related.artist,
|
||||||
|
@ -54,11 +54,12 @@ def on_air(request):
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
diff = last.diffusion
|
||||||
publication = None
|
publication = None
|
||||||
if cms:
|
if cms:
|
||||||
publication = \
|
publication = \
|
||||||
cms.DiffusionPage.objects.filter(
|
cms.DiffusionPage.objects.filter(
|
||||||
diffusion = last.initial or last).first() or \
|
diffusion = diff.initial or diff).first() or \
|
||||||
cms.ProgramPage.objects.filter(
|
cms.ProgramPage.objects.filter(
|
||||||
program = last.program).first()
|
program = last.program).first()
|
||||||
except:
|
except:
|
||||||
|
@ -66,8 +67,8 @@ def on_air(request):
|
||||||
|
|
||||||
last = {
|
last = {
|
||||||
'type': 'diffusion',
|
'type': 'diffusion',
|
||||||
'title': last.program.name,
|
'title': diff.program.name,
|
||||||
'date': last.start,
|
'date': diff.start,
|
||||||
'url': publication.specific.url if publication else None,
|
'url': publication.specific.url if publication else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Command (BaseCommand):
|
||||||
initial__isnull = True
|
initial__isnull = True
|
||||||
).exclude(type = Diffusion.Type.unconfirmed)
|
).exclude(type = Diffusion.Type.unconfirmed)
|
||||||
for diffusion in qs:
|
for diffusion in qs:
|
||||||
if not diffusion.program.page.count():
|
if not diffusion.program.page:
|
||||||
if not hasattr(diffusion.program, '__logged_diff_error'):
|
if not hasattr(diffusion.program, '__logged_diff_error'):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'the program {} has no page; skip the creation of '
|
'the program {} has no page; skip the creation of '
|
||||||
|
@ -80,7 +80,7 @@ class Command (BaseCommand):
|
||||||
page = DiffusionPage.from_diffusion(
|
page = DiffusionPage.from_diffusion(
|
||||||
diffusion, live = False
|
diffusion, live = False
|
||||||
)
|
)
|
||||||
diffusion.program.page.first().add_child(instance = page)
|
diffusion.program.page.add_child(instance = page)
|
||||||
except:
|
except:
|
||||||
import sys
|
import sys
|
||||||
e = sys.exc_info()[0]
|
e = sys.exc_info()[0]
|
||||||
|
|
|
@ -452,10 +452,8 @@ class ProgramPage(Publication):
|
||||||
|
|
||||||
def diffs_to_page(self, diffs):
|
def diffs_to_page(self, diffs):
|
||||||
for diff in diffs:
|
for diff in diffs:
|
||||||
if diff.page.count():
|
if not diff.page:
|
||||||
diff.page_ = diff.page.first()
|
diff.page = ListItem(
|
||||||
else:
|
|
||||||
diff.page_ = ListItem(
|
|
||||||
title = '{}, {}'.format(
|
title = '{}, {}'.format(
|
||||||
self.program.name, diff.date.strftime('%d %B %Y')
|
self.program.name, diff.date.strftime('%d %B %Y')
|
||||||
),
|
),
|
||||||
|
@ -464,7 +462,7 @@ class ProgramPage(Publication):
|
||||||
date = diff.start,
|
date = diff.start,
|
||||||
)
|
)
|
||||||
return [
|
return [
|
||||||
diff.page_ for diff in diffs if diff.page_.live
|
diff.page for diff in diffs if diff.page.live
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -560,8 +558,8 @@ class DiffusionPage(Publication):
|
||||||
'title': '{}, {}'.format(
|
'title': '{}, {}'.format(
|
||||||
diff.program.name, tz.localtime(diff.date).strftime('%d %B %Y')
|
diff.program.name, tz.localtime(diff.date).strftime('%d %B %Y')
|
||||||
),
|
),
|
||||||
'cover': (diff.program.page.count() and \
|
'cover': (diff.program.page and \
|
||||||
diff.program.page.first().cover) or None,
|
diff.program.page.cover) or None,
|
||||||
'date': diff.start,
|
'date': diff.start,
|
||||||
}
|
}
|
||||||
model_kwargs.update(kwargs)
|
model_kwargs.update(kwargs)
|
||||||
|
@ -637,7 +635,7 @@ class DiffusionPage(Publication):
|
||||||
if self.diffusion:
|
if self.diffusion:
|
||||||
# set publish_as
|
# set publish_as
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
self.publish_as = self.diffusion.program.page.first()
|
self.publish_as = self.diffusion.program.page
|
||||||
|
|
||||||
# sync date
|
# sync date
|
||||||
self.date = self.diffusion.start
|
self.date = self.diffusion.start
|
||||||
|
@ -777,8 +775,9 @@ class LogsPage(DatedListPage):
|
||||||
|
|
||||||
logs = []
|
logs = []
|
||||||
for date in context['nav_dates']['dates']:
|
for date in context['nav_dates']['dates']:
|
||||||
items = [ SectionLogsList.as_item(item)
|
items = self.station.on_air(date = date) \
|
||||||
for item in self.station.on_air(date = date) ]
|
.select_related('track','diffusion')
|
||||||
|
items = [ SectionLogsList.as_item(item) for item in items ]
|
||||||
logs.append(
|
logs.append(
|
||||||
(date, reversed(items) if self.reverse else items)
|
(date, reversed(items) if self.reverse else items)
|
||||||
)
|
)
|
||||||
|
|
|
@ -957,16 +957,16 @@ class SectionLogsList(SectionItem):
|
||||||
Supports: Log/Track, Diffusion
|
Supports: Log/Track, Diffusion
|
||||||
"""
|
"""
|
||||||
from aircox_cms.models import DiffusionPage
|
from aircox_cms.models import DiffusionPage
|
||||||
if type(log) == aircox.models.Diffusion:
|
if log.diffusion:
|
||||||
return DiffusionPage.as_item(log)
|
return DiffusionPage.as_item(log.diffusion)
|
||||||
|
|
||||||
related = log.related
|
track = log.track
|
||||||
return ListItem(
|
return ListItem(
|
||||||
title = '{artist} -- {title}'.format(
|
title = '{artist} -- {title}'.format(
|
||||||
artist = related.artist,
|
artist = track.artist,
|
||||||
title = related.title,
|
title = track.title,
|
||||||
),
|
),
|
||||||
headline = related.info,
|
headline = track.info,
|
||||||
date = log.date,
|
date = log.date,
|
||||||
info = '♫',
|
info = '♫',
|
||||||
css_class = 'track'
|
css_class = 'track'
|
||||||
|
|
|
@ -118,7 +118,7 @@ def station_post_saved(sender, instance, created, *args, **kwargs):
|
||||||
|
|
||||||
@receiver(post_save, sender=aircox.Program)
|
@receiver(post_save, sender=aircox.Program)
|
||||||
def program_post_saved(sender, instance, created, *args, **kwargs):
|
def program_post_saved(sender, instance, created, *args, **kwargs):
|
||||||
if not created or instance.page.count():
|
if not created or instance.page:
|
||||||
return
|
return
|
||||||
|
|
||||||
settings = utils.get_station_settings(instance.station)
|
settings = utils.get_station_settings(instance.station)
|
||||||
|
@ -191,7 +191,7 @@ def diffusion_post_saved(sender, instance, created, *args, **kwargs):
|
||||||
page = models.DiffusionPage.from_diffusion(
|
page = models.DiffusionPage.from_diffusion(
|
||||||
instance, live = False
|
instance, live = False
|
||||||
)
|
)
|
||||||
instance.program.page.first().add_child(
|
instance.program.page.add_child(
|
||||||
instance = page
|
instance = page
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -127,8 +127,8 @@ class LogAdmin(ModelAdmin):
|
||||||
menu_label = _('Logs')
|
menu_label = _('Logs')
|
||||||
menu_icon = 'time'
|
menu_icon = 'time'
|
||||||
menu_order = 300
|
menu_order = 300
|
||||||
list_display = ['date', 'station', 'source', 'type', 'comment', 'related']
|
list_display = ['id', 'date', 'station', 'source', 'type', 'comment', 'diffusion', 'sound', 'track']
|
||||||
list_filter = ['date', 'source', 'related_type']
|
list_filter = ['date', 'source', 'diffusion', 'sound', 'track']
|
||||||
|
|
||||||
aircox.models.Log.panels = [
|
aircox.models.Log.panels = [
|
||||||
MultiFieldPanel([
|
MultiFieldPanel([
|
||||||
|
@ -139,11 +139,12 @@ aircox.models.Log.panels = [
|
||||||
]),
|
]),
|
||||||
FieldPanel('type'),
|
FieldPanel('type'),
|
||||||
FieldPanel('comment'),
|
FieldPanel('comment'),
|
||||||
FieldRowPanel([
|
|
||||||
FieldPanel('related_type'),
|
|
||||||
FieldPanel('related_id')
|
|
||||||
]),
|
|
||||||
], heading = _('Log')),
|
], heading = _('Log')),
|
||||||
|
MultiFieldPanel([
|
||||||
|
FieldPanel('diffusion'),
|
||||||
|
FieldPanel('sound'),
|
||||||
|
FieldPanel('track'),
|
||||||
|
], heading = _('Related objects')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -327,7 +328,7 @@ class TodayMenu(GenericMenu):
|
||||||
return MenuItem(label, self.page_url(item), attrs = attrs)
|
return MenuItem(label, self.page_url(item), attrs = attrs)
|
||||||
|
|
||||||
def get_parent(self, item):
|
def get_parent(self, item):
|
||||||
return item.program.page.first()
|
return item.program.page
|
||||||
|
|
||||||
|
|
||||||
@hooks.register('register_admin_menu_item')
|
@hooks.register('register_admin_menu_item')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user