fix and optimize

This commit is contained in:
bkfox 2017-06-29 21:21:28 +02:00
parent 8a129da46a
commit 60cbf18942
9 changed files with 176 additions and 193 deletions

View File

@ -183,8 +183,8 @@ class StationAdmin(admin.ModelAdmin):
@admin.register(Log)
class LogAdmin(admin.ModelAdmin):
list_display = ['id', 'date', 'station', 'source', 'type', 'comment', 'related']
list_filter = ['date', 'source', 'related_type']
list_display = ['id', 'date', 'station', 'source', 'type', 'comment', 'diffusion', 'sound', 'track']
list_filter = ['date', 'source', 'diffusion', 'sound', 'track']
admin.site.register(Port)

View File

@ -15,7 +15,7 @@ from django.conf import settings as main_settings
from django.core.management.base import BaseCommand, CommandError
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:
@ -92,17 +92,18 @@ class Monitor:
if not current_sound or not current_source:
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()
# only streamed
if log and (log.related and not log.related.diffusion):
# only streamed ns
if log and not log.sound.diffusion:
self.trace_sound_tracks(log)
# TODO: expiration
if log and (log.source == current_source.id and \
log.related and
log.related.path == current_sound):
log.sound and
log.sound.path == current_sound):
return
sound = Sound.objects.filter(path = current_sound)
@ -110,7 +111,7 @@ class Monitor:
type = Log.Type.play,
source = current_source.id,
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)
comment = current_sound,
)
@ -120,11 +121,12 @@ class Monitor:
Log tracks for the given sound (for streamed programs); Called by
self.trace
"""
logs = Log.objects.get_for(self.station, model = Track) \
.filter(pk__gt = log.pk)
logs = [ log.related_id for log in logs ]
logs = Log.objects.station(self.station,
track__isnull = False,
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)
if tracks and len(tracks) == len(logs):
return
@ -138,7 +140,7 @@ class Monitor:
type = Log.Type.play,
source = log.source,
date = pos,
related = track,
track = track,
comment = track,
)
@ -157,7 +159,7 @@ class Monitor:
continue
playlist = source.program.sound_set.all() \
.values_list('path', flat = True)
source.playlist = playlist
source.playlist = list(playlist)
def trace_canceled(self):
"""
@ -171,18 +173,18 @@ class Monitor:
type = Diffusion.Type.normal,
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)
for diff in diffs:
if logs.filter(related = diff):
if logs.filter(diffusion = diff):
continue
if diff.start < now:
diff.type = Diffusion.Type.canceled
diff.save()
self.log(
type = Log.Type.other,
related = diff,
diffusion = diff,
comment = 'Diffusion canceled after {} seconds' \
.format(self.cancel_timeout)
)
@ -195,29 +197,29 @@ class Monitor:
station = self.station
now = tz.now()
log = station.played(models = Diffusion).order_by('date').last()
if not log or not log.related.is_date_in_range(now):
log = station.played(diffusion__isnull = False) \
.select_related('diffusion') \
.order_by('date').last()
if not log or not log.diffusion.is_date_in_range(now):
# not running anymore
return None, []
# sound has switched? assume it has been (forced to) stopped
sounds = station.played(models = Sound) \
# last sound source change: end of file reached or forced to stop
sounds = station.played(sound__isnull = False) \
.filter(date__gte = log.date) \
.order_by('date')
# last sound source change: end of file reached
if sounds.count() and sounds.last().source != log.source:
# diffusion is finished: end of sound file reached
return None, []
# last diff is still playing: get remaining playlist
sounds = sounds \
.filter(source = log.source, pk__gt = diff_log.pk) \
.exclude(related__type = Sound.Type.removed)
.values_list('related__path', flat = True)
remaining = log.related.get_archives().exclude(path__in = sounds)
.filter(source = log.source, pk__gt = log.pk) \
.exclude(sound__type = Sound.Type.removed)
return log.related, remaining
remaining = log.diffusion.get_archives().exclude(pk__in = sounds) \
.values_list('path', flat = True)
return log.diffusion, list(remaining)
def __next_diff(self, diff):
"""
@ -230,10 +232,10 @@ class Monitor:
kwargs = {'start__gte': diff.end } if diff else {}
diff = Diffusion.objects \
.at(station, now) \
.filter(type = Diffusion.Type.normal,
sound_type = Sound.Type.archive, **kwargs) \
.distinct().order_by('start').first()
return diff
.filter(type = Diffusion.Type.normal, **kwargs) \
.distinct().order_by('start')
diff = diff.first()
return (diff, diff and diff.playlist or [])
def handle_pl_sync(self, source, playlist, diff = None, date = None):
"""
@ -245,28 +247,30 @@ class Monitor:
dealer.playlist = playlist
if diff and not diff.is_live():
self.log(type = Log.Type.load, source = source.id,
related = diff, date = date)
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 not diff.start <= now:
if not diff or diff.start > date:
return
# live: just log it
if diff.is_live():
if not Log.get_for(object = diff).count():
diff_ = Log.objects.station(self.station) \
.filter(diffusion = diff)
if not diff_.count():
self.log(type = Log.Type.live, source = source.id,
related = diff, date = date)
diffusion = diff, date = date)
return
# enable dealer
if not dealer.active:
dealer.active = True
self.log(type = Log.Type.play, source = source.id,
related = diff, date = date)
diffusion = diff, date = date)
def handle(self):
"""
@ -280,15 +284,15 @@ class Monitor:
now = tz.now()
# current and next diffs
current_diff, current_pl = self.__current_diff()
next_diff, next_pl = self.__next_diff(diff)
current_diff, remaining_pl = self.__current_diff()
next_diff, next_pl = self.__next_diff(current_diff)
# playlist
dealer.active = bool(current_pl)
playlist = current_pl + next_pl
dealer.active = bool(remaining_pl)
playlist = remaining_pl + next_pl
self.handle_pl_sync(dealer, playlist, next_diff, now)
self.handle_diff_start(dealer, next_diff)
self.handle_diff_start(dealer, next_diff, now)
class Command (BaseCommand):

View File

@ -50,7 +50,6 @@ class RelatedManager(models.Manager):
qs = qs.filter(related_id = object.pk)
return qs
class Related(models.Model):
"""
Add a field "related" of type GenericForeignKey, plus utilities.
@ -148,7 +147,6 @@ class Track(Related):
verbose_name = _('Track')
verbose_name_plural = _('Tracks')
#
# Station related classes
#
@ -180,7 +178,7 @@ class Station(Nameable):
__dealer = None
__streamer = None
def __prepare(self):
def __prepare_controls(self):
import aircox.controllers as controllers
if not self.__streamer:
self.__streamer = controllers.Streamer(station = self)
@ -215,12 +213,12 @@ class Station(Nameable):
"""
Audio sources, dealer included
"""
self.__prepare()
self.__prepare_controls()
return self.__sources
@property
def dealer(self):
self.__prepare()
self.__prepare_controls()
return self.__dealer
@property
@ -228,86 +226,25 @@ class Station(Nameable):
"""
Audio controller for the station
"""
self.__prepare()
self.__prepare_controls()
return self.__streamer
def played(self, models, archives = True, include_live = True):
def played(self, *args, **kwargs):
"""
Call Log.objects.played for this station
"""
return Log.objects.played(self, models, archives, include_live)
@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
return Log.objects.played(self, *args, **kwargs)
def on_air(self, date = None, count = 0):
"""
Return a list of what happened on air, based on logs and
diffusions informations. The list is sorted by -date.
Return a queryset of what happened on air, based on logs and
diffusions informations. The queryset is sorted by -date.
* date: only for what happened on this date;
* count: number of items to retrieve if not zero;
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.
The list contains:
* track logs: for the streamed programs;
* diffusion: for the scheduled diffusions;
"""
# FIXME: as an iterator?
# TODO argument to get sound instead of tracks
@ -315,21 +252,39 @@ class Station(Nameable):
raise ValueError('at least one argument must be set')
# 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():
return []
if date:
logs = Log.objects.at_for(self, date, model = Track)
diffs = Diffusion.objects.at(self, date)
logs = Log.objects.at(self, date)
diffs = Diffusion.objects.at(self, date, type = Diffusion.Type.normal) \
.order_by('-start')
else:
logs = Log.objects.get_for(self, model = Track)
diffs = Diffusion.objects
logs = logs.filter(station = self)
logs = Log.objects
diffs = Diffusion.objects.filter(type = Diffusion.Type.normal,
start__lte = tz.now()) \
.order_by('-start')[:count]
diffs = diffs.filter(program__station = self) \
.filter(type = Diffusion.Type.normal) \
.filter(start__lte = tz.now())
return self.__mix_logs_and_diff(diffs, logs, count)
q = models.Q(diffusion__isnull = False) | \
models.Q(track__isnull = False)
logs = logs.filter(q).order_by('-date')
# 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):
if not self.path:
@ -348,9 +303,9 @@ class Station(Nameable):
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
return qs.filter(station = station)
return qs.filter(station = station, **kwargs)
class Program(Nameable):
"""
@ -730,11 +685,11 @@ class Schedule(models.Model):
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
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
@ -770,7 +725,7 @@ class DiffusionManager(models.Manager):
if next:
# include also diffusions of the next day
filters |= models.Q(start__gte = start)
qs = qs.filter(filters)
qs = qs.filter(filters, **kwargs)
return self.station(station, qs).order_by('start').distinct()
def after(self, station, date = None, qs = None):
@ -867,7 +822,12 @@ class Diffusion(models.Model):
"""
List of archives' path; uses get_archives
"""
return self.get_archives().values_list('path', flat = True)
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):
"""
@ -1229,63 +1189,51 @@ class Port (models.Model):
)
class LogManager(RelatedManager):
def station(self, station, qs = None):
class LogManager(models.Manager):
def station(self, station, qs = None, **kwargs):
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):
qs = super().get_for(*args, **kwargs)
return self.station(station, qs) if station else qs
def _at(self, date = None, qs = None):
def _at(self, date = None, qs = None, **kwargs):
start, end = utils.date_range(date)
qs = self if qs is None else qs
return qs.filter(date__gte = start,
date__lte = end)
return qs.filter(date__gte = start, date__lte = end, **kwargs)
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
in their range.
"""
qs = self._at(date, qs)
qs = self._at(date, qs, **kwargs)
return self.station(station, qs) if station else qs
def at_for(self, station, date, object = None, model = None, qs = None):
"""
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, include_live = True):
def played(self, station, archives = True, include_live = True,
**kwargs):
"""
Return a queryset of the played elements' log for the given
station and model. This queryset is ordered by date ascending
* station: related station
* models: a model or a list of models
* archives: if false, exclude log of diffusion's archives from
the queryset;
* include_live: include diffusion that have no archive
* kwargs: extra filter kwargs
"""
if include_live:
qs = self.get_for(station, model = models) \
.filter(type__in = (Log.Type.play, Log.Type.live))
qs = self.filter(type__in = (Log.Type.play, Log.Type.live),
**kwargs)
else:
qs = self.get_for(station, model = models) \
.filter(type = Log.Type.play)
qs = self.filter(type = Log.Type.play, **kwargs)
if not archives and station.dealer:
qs = qs.exclude(
source = station.dealer.id,
related_type = ContentType.objects.get_for_model(Sound)
sound__isnull = False
)
return qs.order_by('date')
class Log(Related):
class Log(models.Model):
"""
Log sounds and diffusions that are played on the station.
@ -1337,6 +1285,7 @@ class Log(Related):
date = models.DateTimeField(
_('date'),
default=tz.now,
db_index = True,
)
comment = models.CharField(
_('comment'),
@ -1344,6 +1293,25 @@ class Log(Related):
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()
@property
@ -1351,12 +1319,16 @@ class Log(Related):
"""
Calculated end using self.related informations
"""
if self.related_type == Diffusion:
return self.related.end
if self.related_type == Sound:
return self.date + to_timedelta(self.duration)
if self.diffusion:
return self.diffusion.end
if self.sound:
return self.date + to_timedelta(sound.duration)
return self.date
@property
def related(self):
return self.diffusion or self.sound or self.track
def is_expired(self, date = None):
"""
Return True if the log is expired. Note that it only check
@ -1367,11 +1339,19 @@ class Log(Related):
return self.end < date
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',
str(self),
self.comment or '',
' -- {} #{}'.format(self.related_type, self.related_id)
if self.related else ''
' (' + ', '.join(r) + ')' if r else ''
)
def __str__(self):
@ -1379,4 +1359,3 @@ class Log(Related):
self.pk, self.date.strftime('%Y/%m/%d %H:%M'), self.source
)

View File

@ -40,12 +40,12 @@ def on_air(request):
else:
station = stations.stations.first()
last = station.on_air(count = 10)
if not last:
on_air = station.on_air(count = 10).select_related('track','diffusion')
if not on_air.count():
return HttpResponse('')
last = last[0]
if type(last) == models.Log:
last = on_air.last()
if last.track:
last = {
'type': 'track',
'artist': last.related.artist,
@ -54,11 +54,12 @@ def on_air(request):
}
else:
try:
diff = last.diffusion
publication = None
if cms:
publication = \
cms.DiffusionPage.objects.filter(
diffusion = last.initial or last).first() or \
diffusion = diff.initial or diff).first() or \
cms.ProgramPage.objects.filter(
program = last.program).first()
except:
@ -66,8 +67,8 @@ def on_air(request):
last = {
'type': 'diffusion',
'title': last.program.name,
'date': last.start,
'title': diff.program.name,
'date': diff.start,
'url': publication.specific.url if publication else None,
}

View File

@ -64,7 +64,7 @@ class Command (BaseCommand):
initial__isnull = True
).exclude(type = Diffusion.Type.unconfirmed)
for diffusion in qs:
if not diffusion.program.page.count():
if not diffusion.program.page:
if not hasattr(diffusion.program, '__logged_diff_error'):
logger.warning(
'the program {} has no page; skip the creation of '
@ -80,7 +80,7 @@ class Command (BaseCommand):
page = DiffusionPage.from_diffusion(
diffusion, live = False
)
diffusion.program.page.first().add_child(instance = page)
diffusion.program.page.add_child(instance = page)
except:
import sys
e = sys.exc_info()[0]

View File

@ -452,10 +452,8 @@ class ProgramPage(Publication):
def diffs_to_page(self, diffs):
for diff in diffs:
if diff.page.count():
diff.page_ = diff.page.first()
else:
diff.page_ = ListItem(
if not diff.page:
diff.page = ListItem(
title = '{}, {}'.format(
self.program.name, diff.date.strftime('%d %B %Y')
),
@ -464,7 +462,7 @@ class ProgramPage(Publication):
date = diff.start,
)
return [
diff.page_ for diff in diffs if diff.page_.live
diff.page for diff in diffs if diff.page.live
]
@property
@ -560,8 +558,8 @@ class DiffusionPage(Publication):
'title': '{}, {}'.format(
diff.program.name, tz.localtime(diff.date).strftime('%d %B %Y')
),
'cover': (diff.program.page.count() and \
diff.program.page.first().cover) or None,
'cover': (diff.program.page and \
diff.program.page.cover) or None,
'date': diff.start,
}
model_kwargs.update(kwargs)
@ -637,7 +635,7 @@ class DiffusionPage(Publication):
if self.diffusion:
# set publish_as
if not self.pk:
self.publish_as = self.diffusion.program.page.first()
self.publish_as = self.diffusion.program.page
# sync date
self.date = self.diffusion.start
@ -777,8 +775,9 @@ class LogsPage(DatedListPage):
logs = []
for date in context['nav_dates']['dates']:
items = [ SectionLogsList.as_item(item)
for item in self.station.on_air(date = date) ]
items = self.station.on_air(date = date) \
.select_related('track','diffusion')
items = [ SectionLogsList.as_item(item) for item in items ]
logs.append(
(date, reversed(items) if self.reverse else items)
)

View File

@ -957,16 +957,16 @@ class SectionLogsList(SectionItem):
Supports: Log/Track, Diffusion
"""
from aircox_cms.models import DiffusionPage
if type(log) == aircox.models.Diffusion:
return DiffusionPage.as_item(log)
if log.diffusion:
return DiffusionPage.as_item(log.diffusion)
related = log.related
track = log.track
return ListItem(
title = '{artist} -- {title}'.format(
artist = related.artist,
title = related.title,
artist = track.artist,
title = track.title,
),
headline = related.info,
headline = track.info,
date = log.date,
info = '',
css_class = 'track'

View File

@ -118,7 +118,7 @@ def station_post_saved(sender, instance, created, *args, **kwargs):
@receiver(post_save, sender=aircox.Program)
def program_post_saved(sender, instance, created, *args, **kwargs):
if not created or instance.page.count():
if not created or instance.page:
return
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(
instance, live = False
)
instance.program.page.first().add_child(
instance.program.page.add_child(
instance = page
)

View File

@ -327,7 +327,7 @@ class TodayMenu(GenericMenu):
return MenuItem(label, self.page_url(item), attrs = attrs)
def get_parent(self, item):
return item.program.page.first()
return item.program.page
@hooks.register('register_admin_menu_item')