diff --git a/aircox/admin.py b/aircox/admin.py index 95414b7..3ac5ba9 100755 --- a/aircox/admin.py +++ b/aircox/admin.py @@ -164,9 +164,18 @@ class ScheduleAdmin(admin.ModelAdmin): rerun.short_description = _('Rerun') rerun.boolean = True + list_filter = ['frequency', 'program'] - list_display = ['id', 'program_name', 'frequency', 'day', 'date', 'time', 'timezone', 'duration', 'rerun'] - list_editable = ['frequency', 'date', 'time', 'timezone', 'duration'] + list_display = ['id', 'program_name', 'frequency', 'day', 'date', + 'time', 'duration', 'timezone', 'rerun'] + list_editable = ['time', 'timezone', 'duration'] + + + def get_readonly_fields(self, request, obj=None): + if obj: + return ['program', 'date', 'frequency'] + else: + return [] @admin.register(Track) diff --git a/aircox/management/commands/sounds_monitor.py b/aircox/management/commands/sounds_monitor.py index efce5a9..28911f4 100755 --- a/aircox/management/commands/sounds_monitor.py +++ b/aircox/management/commands/sounds_monitor.py @@ -172,12 +172,9 @@ class SoundInfo: self.hour or 0, self.minute or 0) date = tz.get_current_timezone().localize(date) - diffusion = Diffusion.objects.after( - program.station, - date, - program = program, - initial__isnull = True, - ).first() + qs = Diffusion.objects.station(program.station).after(date) \ + .filter(program = program, initial__isnull = True) + diffusion = qs.first() if not diffusion: return diff --git a/aircox/management/commands/streamer.py b/aircox/management/commands/streamer.py index b9ce5b8..a1c6f45 100755 --- a/aircox/management/commands/streamer.py +++ b/aircox/management/commands/streamer.py @@ -245,14 +245,14 @@ class Monitor: if not self.cancel_timeout: return - diffs = Diffusions.objects.at(self.station).filter( + qs = Diffusions.objects.station(self.station).at().filter( type = Diffusion.Type.normal, sound__type = Sound.Type.archive, ) logs = station.raw_on_air(diffusion__isnull = False) date = tz.now() - datetime.timedelta(seconds = self.cancel_timeout) - for diff in diffs: + for diff in qs: if logs.filter(diffusion = diff): continue if diff.start < now: @@ -305,14 +305,13 @@ class Monitor: If diff is given, it is the one to be played right after it. """ station = self.station - now = tz.now() kwargs = {'start__gte': diff.end } if diff else {} - diff = Diffusion.objects \ - .at(station, now) \ - .filter(type = Diffusion.Type.normal, **kwargs) \ - .distinct().order_by('start') - diff = diff.first() + kwargs['type'] = Diffusion.Type.normal + + qs = Diffusion.objects.station(station).at().filter(**kwargs) \ + .distinct().order_by('start') + diff = qs.first() return (diff, diff and diff.get_playlist(archive = True) or []) def handle_pl_sync(self, source, playlist, diff = None, date = None): diff --git a/aircox/models.py b/aircox/models.py index 87ed148..8a845e2 100755 --- a/aircox/models.py +++ b/aircox/models.py @@ -232,11 +232,11 @@ class Station(Nameable): self.__prepare_controls() return self.__streamer - def raw_on_air(self, *args, **kwargs): + def raw_on_air(self, **kwargs): """ Forward call to Log.objects.on_air for this station """ - return Log.objects.on_air(self, *args, **kwargs) + return Log.objects.station(self).on_air().filter(**kwargs) def on_air(self, date = None, count = 0, no_cache = False): """ @@ -266,11 +266,10 @@ class Station(Nameable): now = tz.now() if date: - logs = Log.objects.at(self, date) - diffs = Diffusion.objects \ - .at(self, date, type = Diffusion.Type.normal) \ - .filter(start__lte = now) \ - .order_by('-start') + logs = Log.objects.station(self).at(date) + diffs = Diffusion.objects.station(self).at(date) \ + .filter(start__lte = now, type = Diffusion.Type.normal) \ + .order_by('-start') else: logs = Log.objects diffs = Diffusion.objects \ @@ -568,15 +567,11 @@ class Schedule(models.Model): if not initial: return - before, now = self.__initial, self.__dict__ - before, now = { - f: getattr(before, f) for f in fields - if hasattr(before, f) - }, { - f: getattr(now, f) for f in fields - if hasattr(now, f) - } - return before == now + this = self.__dict__ + for field in fields: + if initial.get(field) != this.get(field): + return True + return False def match(self, date = None, check_time = True): """ @@ -727,7 +722,10 @@ class Schedule(models.Model): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.__initial = self.__dict__.copy() + + # initial only if it has been yet saved + if self.pk: + self.__initial = self.__dict__.copy() def __str__(self): return ' | '.join([ '#' + str(self.id), self.program.name, @@ -747,12 +745,14 @@ class Schedule(models.Model): verbose_name_plural = _('Schedules') -class DiffusionManager(models.Manager): - def station(self, station, qs = None, **kwargs): - qs = self if qs is None else qs - return qs.filter(program__station = station, **kwargs) +class DiffusionQuerySet(models.QuerySet): + def station(self, station, **kwargs): + return self.filter(program__station = station, **kwargs) - def at(self, station, date = None, next = False, qs = None, **kwargs): + def program(self, program): + return self.filter(program = program) + + def at(self, date = None, next = False, **kwargs): """ Return diffusions occuring at the given date, ordered by +start @@ -768,7 +768,7 @@ class DiffusionManager(models.Manager): # note: we work with localtime date = utils.date_or_default(date, keep_type = True) - qs = self if qs is None else qs + qs = self filters = None if isinstance(date, datetime.datetime): # use datetime: we want diffusion that occurs around this @@ -789,25 +789,23 @@ class DiffusionManager(models.Manager): # include also diffusions of the next day filters |= models.Q(start__gte = start) qs = qs.filter(filters, **kwargs) - return self.station(station, qs).order_by('start').distinct() + return qs.order_by('start').distinct() - def after(self, station, date = None, **kwargs): + def after(self, date = None, **kwargs): """ Return a queryset of diffusions that happen after the given date. """ date = utils.date_or_default(date, keep_type = True) - return self.station(station, start__gte = date, **kwargs)\ - .order_by('start') + return self.filter(start__gte = date, **kwargs).order_by('start') - def before(self, station, date = None, **kwargs): + def before(self, date = None, **kwargs): """ Return a queryset of diffusions that finish before the given date. """ date = utils.date_or_default(date) - return self.station(station, end__lte = date, **kwargs) \ - .order_by('start') + return self.filter(end__lte = date, **kwargs).order_by('start') class Diffusion(models.Model): @@ -828,7 +826,7 @@ class Diffusion(models.Model): - cancel: the diffusion has been canceled - stop: the diffusion has been manually stopped """ - objects = DiffusionManager() + objects = DiffusionQuerySet.as_manager() class Type(IntEnum): normal = 0x00 @@ -1281,26 +1279,18 @@ class Port (models.Model): ) -class LogManager(models.Manager): - def station(self, station, *args, **kwargs): - return self.filter(*args, station = station, **kwargs) +class LogQuerySet(models.QuerySet): + def station(self, station): + return self.filter(station = station) - def _at(self, date = None, *args, **kwargs): + def at(self, date = None): start, end = utils.date_range(date) # return qs.filter(models.Q(end__gte = start) | # models.Q(date__lte = end)) - return self.filter(*args, date__gte = start, date__lte = end, **kwargs) - - def at(self, station = None, date = None, *args, **kwargs): - """ - Return a queryset of logs that have the given date - in their range. - """ - qs = self._at(date, *args, **kwargs) - return qs.filter(station = station) if station else qs + return self.filter(date__gte = start, date__lte = end) # TODO: rename on_air + rename Station.on_air into sth like regular_on_air - def on_air(self, station, date = None, **kwargs): + def on_air(self, date = None): """ Return a queryset of the played elements' log for the given station and model. This queryset is ordered by date ascending @@ -1310,11 +1300,11 @@ class LogManager(models.Manager): * kwargs: extra filter kwargs """ if date: - qs = self.at(station, date) + qs = self.at(date) else: qs = self - qs = qs.filter(type = Log.Type.on_air, **kwargs) + qs = qs.filter(type = Log.Type.on_air) return qs.order_by('date') @staticmethod @@ -1399,7 +1389,7 @@ class LogManager(models.Manager): if os.path.exists(path) and not force: return -1 - qs = self.at(station, date) + qs = self.station(station).at(date) if not qs.exists(): return 0 @@ -1516,7 +1506,7 @@ class Log(models.Model): on_delete=models.SET_NULL, ) - objects = LogManager() + objects = LogQuerySet.as_manager() def estimate_end(self): """ diff --git a/aircox/signals.py b/aircox/signals.py index b480d14..72d7ef1 100755 --- a/aircox/signals.py +++ b/aircox/signals.py @@ -39,62 +39,44 @@ def user_default_groups(sender, instance, created, *args, **kwargs): instance.groups.add(group) -# FIXME: avoid copy of the code in schedule_post_saved and -# schedule_pre_delete - @receiver(post_save, sender=models.Schedule) -def schedule_post_saved(sender, instance, created, *args, **kwargs): - return - # TODO: case instance.program | instance.frequency has changed - if not instance.program.sync: +def schedule_post_save(sender, instance, created, *args, **kwargs): + """ + Handles Schedule's time, duration and timezone changes and update + corresponding diffusions accordingly. + """ + if created or not instance.program.sync or \ + not instance.changed(['time','duration','timezone']): return initial = instance._Schedule__initial - if not initial or not instance.changed(['date','duration', 'frequency']): - return - - if not initial.get('date') or not initial.get('duration') \ - or not initial.get('frequency') or \ - initial.frequency != instance.frequency: - return - - # old schedule and timedelta - old = models.Schedule(**{ key: initial.get(key) - for key in ('date','timezone','duration','frequency') + initial = models.Schedule(**{ k: v + for k, v in instance._Schedule__initial.items() + if not k.startswith('_') }) - # change: day, time, duration, frequence => TODO - delta = (instance.date - old.date) + \ - (instance.time - old.time) + today = tz.datetime.today() + delta = instance.normalize(today) - \ + initial.normalize(today) - qs = models.Diffusion.objects.station( - instance.program.station, - ) - - pks = [ item.pk for item in qs if old.match(item.date) ] + qs = models.Diffusion.objects.program(instance.program).after() + pks = [ d.pk for d in qs if initial.match(d.date) ] qs.filter(pk__in = pks).update( start = F('start') + delta, end = F('start') + delta + utils.to_timedelta(instance.duration) ) - return + @receiver(pre_delete, sender=models.Schedule) def schedule_pre_delete(sender, instance, *args, **kwargs): + """ + Delete later corresponding diffusion to a changed schedule. + """ if not instance.program.sync: return - initial = instance._Schedule__initial - if not initial or not instance.changed(['date','duration', 'frequency']): - return - - old = models.Schedule(**{ key: initial.get(key) - for key in ('date','timezone','duration','frequency') - }) - - qs = models.Diffusion.objects.station( - instance.program.station, - ) - - pks = [ item.pk for item in qs if old.match(item.date) ] + qs = models.Diffusion.objects.program(instance.program).after() + pks = [ d.pk for d in qs if instance.match(d.date) ] qs.filter(pk__in = pks).delete() +