fix and optimize
This commit is contained in:
		@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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):
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										223
									
								
								aircox/models.py
									
									
									
									
									
								
							
							
						
						
									
										223
									
								
								aircox/models.py
									
									
									
									
									
								
							@ -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
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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]
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
@ -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'
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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')
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user