From 76055c742a735ee6813ee90b7930597b3ab9cf84 Mon Sep 17 00:00:00 2001 From: bkfox Date: Tue, 25 Oct 2016 16:50:58 +0200 Subject: [PATCH] start to work on stats --- aircox/controllers.py | 2 +- .../management/commands/diffusions_monitor.py | 2 +- aircox/management/commands/streamer.py | 3 +- aircox/models.py | 94 +++++++++---------- .../{controllers => config}/liquidsoap.liq | 0 .../templates/aircox/controllers/stats.html | 46 +++++++++ aircox/urls.py | 3 +- aircox/views.py | 92 +++++++++++++++++- 8 files changed, 189 insertions(+), 53 deletions(-) rename aircox/templates/aircox/{controllers => config}/liquidsoap.liq (100%) create mode 100644 aircox/templates/aircox/controllers/stats.html diff --git a/aircox/controllers.py b/aircox/controllers.py index 335e572..0147faf 100644 --- a/aircox/controllers.py +++ b/aircox/controllers.py @@ -19,7 +19,7 @@ class Streamer: """ Related station """ - template_name = 'aircox/controllers/liquidsoap.liq' + template_name = 'aircox/config/liquidsoap.liq' """ If set, use this template in order to generated the configuration file in self.path file diff --git a/aircox/management/commands/diffusions_monitor.py b/aircox/management/commands/diffusions_monitor.py index 9bf0a3b..6565c1b 100644 --- a/aircox/management/commands/diffusions_monitor.py +++ b/aircox/management/commands/diffusions_monitor.py @@ -137,7 +137,7 @@ class Command (BaseCommand): group.add_argument( '--check', action='store_true', help='check unconfirmed later diffusions from the given ' - 'date again'\'t schedule. If no schedule is found, remove ' + 'date agains\'t schedule. If no schedule is found, remove ' 'it.' ) diff --git a/aircox/management/commands/streamer.py b/aircox/management/commands/streamer.py index ac0f84c..f390eb6 100644 --- a/aircox/management/commands/streamer.py +++ b/aircox/management/commands/streamer.py @@ -105,7 +105,8 @@ class Monitor: source = current_source.id, date = tz.now(), related = sound[0] if sound else None, - comment = None if sound else current_sound, + # keep sound path (if sound is removed, we keep that info) + comment = current_sound, ) def trace_sound_tracks(self, log): diff --git a/aircox/models.py b/aircox/models.py index 953b7c8..b9497a6 100755 --- a/aircox/models.py +++ b/aircox/models.py @@ -377,52 +377,6 @@ class Program(Nameable): return qs[0] if qs else None -class DiffusionManager(models.Manager): - def get_at(self, date = None, next = False): - """ - Return a queryset of diffusions that have the given date - in their range. - - If date is a datetime.date object, check only against the - date. - """ - date = date or tz.now() - if not issubclass(type(date), datetime.datetime): - return self.filter( - models.Q(start__contains = date) | \ - models.Q(end__contains = date) - ) - - if not next: - return self.filter(start__lte = date, end__gte = date) \ - .order_by('start') - - return self.filter( - models.Q(start__lte = date, end__gte = date) | - models.Q(start__gte = date), - ).order_by('start') - - def get_after(self, date = None): - """ - Return a queryset of diffusions that happen after the given - date. - """ - date = date_or_default(date) - return self.filter( - start__gte = date, - ).order_by('start') - - def get_before(self, date): - """ - Return a queryset of diffusions that finish before the given - date. - """ - date = date_or_default(date) - return self.filter( - end__lte = date, - ).order_by('start') - - class Stream(models.Model): """ When there are no program scheduled, it is possible to play sounds @@ -671,6 +625,52 @@ class Schedule(models.Model): verbose_name_plural = _('Schedules') +class DiffusionManager(models.Manager): + def get_at(self, date = None, next = False): + """ + Return a queryset of diffusions that have the given date + in their range. + + If date is a datetime.date object, check only against the + date. + """ + date = date or tz.now() + if not issubclass(type(date), datetime.datetime): + return self.filter( + models.Q(start__contains = date) | \ + models.Q(end__contains = date) + ) + + if not next: + return self.filter(start__lte = date, end__gte = date) \ + .order_by('start') + + return self.filter( + models.Q(start__lte = date, end__gte = date) | + models.Q(start__gte = date), + ).order_by('start') + + def get_after(self, date = None): + """ + Return a queryset of diffusions that happen after the given + date. + """ + date = date_or_default(date) + return self.filter( + start__gte = date, + ).order_by('start') + + def get_before(self, date): + """ + Return a queryset of diffusions that finish before the given + date. + """ + date = date_or_default(date) + return self.filter( + end__lte = date, + ).order_by('start') + + class Diffusion(models.Model): """ A Diffusion is an occurrence of a Program that is scheduled on the @@ -1036,7 +1036,7 @@ class Log(Related): Log sounds and diffusions that are played on the station. This only remember what has been played on the outputs, not on each - track; Source designate here which source is responsible of that. + source; Source designate here which source is responsible of that. """ class Type(IntEnum): stop = 0x00 diff --git a/aircox/templates/aircox/controllers/liquidsoap.liq b/aircox/templates/aircox/config/liquidsoap.liq similarity index 100% rename from aircox/templates/aircox/controllers/liquidsoap.liq rename to aircox/templates/aircox/config/liquidsoap.liq diff --git a/aircox/templates/aircox/controllers/stats.html b/aircox/templates/aircox/controllers/stats.html new file mode 100644 index 0000000..fae3b09 --- /dev/null +++ b/aircox/templates/aircox/controllers/stats.html @@ -0,0 +1,46 @@ +{% load i18n %} + +
+ {% for stats in statistics %} +
+
+

{{ stats.station.name }}

+

- {{ stats.date|date:'l d F Y' }}

+
+ + + + + {# Translators "Header for statistics view" #} + + + + {% for item in stats.items %} + + + {# TODO: logs #} + + {% for tag,count in item.tags %} + + {% endfor %} + + {% endfor %} + + + + + {% for tag, count, average in stats.tags %} + + {% endfor %} + +
{% trans "Date" %}{% trans "Diffusion or sound played" %} +
{{ item.date|date:"H:i" }}{{ item.program.name }}{{ tag }}: {{ count }}
{{ stats.date|date:'d/m/Y' }}{% trans "Total and average" %} ({{ stats.tracks_count }}){{ tag }}: {{ count }} / {{ average|floatformat }}%
+
+ {% endfor %} +
+ + + + + + diff --git a/aircox/urls.py b/aircox/urls.py index 43edb47..437bbf9 100644 --- a/aircox/urls.py +++ b/aircox/urls.py @@ -4,6 +4,7 @@ import aircox.views as views urls = [ url(r'^on_air', views.on_air, name='aircox.on_air'), - url(r'^monitor', views.Monitor.as_view(), name='aircox.monitor') + url(r'^monitor', views.Monitor.as_view(), name='aircox.monitor'), + url(r'^stats', views.StatisticsView.as_view(), name='aircox.stats'), ] diff --git a/aircox/views.py b/aircox/views.py index f5153bb..1a41c77 100755 --- a/aircox/views.py +++ b/aircox/views.py @@ -1,5 +1,7 @@ import json +import datetime +from django.db.models import Count from django.views.generic.base import View, TemplateResponseMixin from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpResponse, Http404 @@ -82,7 +84,7 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin): stations.fetch() return { 'stations': stations.stations } - def get (self, request = None, **kwargs): + def get(self, request = None, **kwargs): if not request.user.is_active: return Http404() @@ -90,7 +92,7 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin): context = self.get_context_data(**kwargs) return render(request, self.template_name, context) - def post (self, request = None, **kwargs): + def post(self, request = None, **kwargs): if not request.user.is_active: return Http404() @@ -121,6 +123,92 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin): return HttpResponse('') +class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin): + template_name = 'aircox/controllers/stats.html' + + class Stats: + station = None + date = None + items = None + """ + Log or Diffusion object that has been diffused by date. These + objects have extra fields: + - tags: [ (tag_name, tag_count), ...] + - tracks_count: total count of tracks + """ + tags = None + """ + Total of played track's tags: [(tag_name, tag_count, tag_average), ...] + on the station for the given date. Note: tag_average is in % + """ + tracks_count = 0 + + def __init__(self, **kwargs): + self.items = [] + self.tags = [] + self.__dict__.update(kwargs) + + def get_stats(self, station, date): + """ + Return statistics for the given station and date. + """ + items = station.on_air(date) + stats = self.Stats(station = station, items = items, date = date) + + sums = {} + total = 0 + + for item in items: + qs = models.Track.objects.get_for(item) + item.tracks = qs + item.tracks_count = qs.count() + + qs = qs.values('tags__name').annotate(count = Count('tags__name')) \ + .order_by('tags__name') + item.tags = [ + (q['tags__name'], q['count']) + for q in qs if q['tags__name'] + ] + for name, count in item.tags: + sums[name] = (sums.get(name) or 0) + count + + total += item.tracks_count + + stats.tracks_count = total + stats.tags = [ + (name, count, count / total * 100) + for name, count in sums.items() + ] + stats.tags.sort(key=lambda s: s[0]) + return stats + + def get_context_data(self, **kwargs): + context = {} + date = datetime.date.today() + + try: + GET = self.request.GET + year = int(GET["year"]) if 'year' in GET else date.year + month = int(GET["month"]) if 'month' in GET else date.month + day = int(GET["day"]) if 'day' in GET else date.day + date = datetime.date(year, month, day) + except: + pass + + context["statistics"] = [ + self.get_stats(station, date) + for station in models.Station.objects.all() + ] + + return context + + def get(self, request = None, **kwargs): + if not request.user.is_active: + return Http404() + + self.request = request + context = self.get_context_data(**kwargs) + return render(request, self.template_name, context)