forked from rc/aircox
		
	start to work on stats
This commit is contained in:
		@ -19,7 +19,7 @@ class Streamer:
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    Related station
 | 
					    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
 | 
					    If set, use this template in order to generated the configuration
 | 
				
			||||||
    file in self.path file
 | 
					    file in self.path file
 | 
				
			||||||
 | 
				
			|||||||
@ -137,7 +137,7 @@ class Command (BaseCommand):
 | 
				
			|||||||
        group.add_argument(
 | 
					        group.add_argument(
 | 
				
			||||||
            '--check', action='store_true',
 | 
					            '--check', action='store_true',
 | 
				
			||||||
            help='check unconfirmed later diffusions from the given '
 | 
					            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.'
 | 
					                 'it.'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -105,7 +105,8 @@ class Monitor:
 | 
				
			|||||||
            source = current_source.id,
 | 
					            source = current_source.id,
 | 
				
			||||||
            date = tz.now(),
 | 
					            date = tz.now(),
 | 
				
			||||||
            related = sound[0] if sound else None,
 | 
					            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):
 | 
					    def trace_sound_tracks(self, log):
 | 
				
			||||||
 | 
				
			|||||||
@ -377,52 +377,6 @@ class Program(Nameable):
 | 
				
			|||||||
        return qs[0] if qs else None
 | 
					        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):
 | 
					class Stream(models.Model):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    When there are no program scheduled, it is possible to play sounds
 | 
					    When there are no program scheduled, it is possible to play sounds
 | 
				
			||||||
@ -671,6 +625,52 @@ class Schedule(models.Model):
 | 
				
			|||||||
        verbose_name_plural = _('Schedules')
 | 
					        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):
 | 
					class Diffusion(models.Model):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    A Diffusion is an occurrence of a Program that is scheduled on the
 | 
					    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.
 | 
					    Log sounds and diffusions that are played on the station.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This only remember what has been played on the outputs, not on each
 | 
					    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):
 | 
					    class Type(IntEnum):
 | 
				
			||||||
        stop = 0x00
 | 
					        stop = 0x00
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										46
									
								
								aircox/templates/aircox/controllers/stats.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								aircox/templates/aircox/controllers/stats.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					{% load i18n %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div id='stats'>
 | 
				
			||||||
 | 
					    {% for stats in statistics %}
 | 
				
			||||||
 | 
					    <section class="station">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1>{{ stats.station.name }}</h1>
 | 
				
			||||||
 | 
					            <h2>- {{ stats.date|date:'l d F Y' }}</h2>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <table border=1>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th>{% trans "Date" %}</th>
 | 
				
			||||||
 | 
					                {# Translators "Header for statistics view" #}
 | 
				
			||||||
 | 
					                <th>{% trans "Diffusion or sound played" %}
 | 
				
			||||||
 | 
					                <th colspan="100"></th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {% for item in stats.items %}
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>{{ item.date|date:"H:i" }}</td>
 | 
				
			||||||
 | 
					                {# TODO: logs #}
 | 
				
			||||||
 | 
					                <td>{{ item.program.name }}</td>
 | 
				
			||||||
 | 
					                {% for tag,count in item.tags %}
 | 
				
			||||||
 | 
					                <td>{{ tag }}: {{ count }}</td>
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>{{ stats.date|date:'d/m/Y' }}</td>
 | 
				
			||||||
 | 
					                <td>{% trans "Total and average" %} ({{ stats.tracks_count }})</td>
 | 
				
			||||||
 | 
					                {% for tag, count, average in stats.tags %}
 | 
				
			||||||
 | 
					                <td>{{ tag }}: <b>{{ count }} / {{ average|floatformat }}%</b></td>
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					    {% endfor %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -4,6 +4,7 @@ import aircox.views as views
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
urls = [
 | 
					urls = [
 | 
				
			||||||
    url(r'^on_air', views.on_air, name='aircox.on_air'),
 | 
					    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'),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
import json
 | 
					import json
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db.models import Count
 | 
				
			||||||
from django.views.generic.base import View, TemplateResponseMixin
 | 
					from django.views.generic.base import View, TemplateResponseMixin
 | 
				
			||||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
					from django.contrib.auth.mixins import LoginRequiredMixin
 | 
				
			||||||
from django.http import HttpResponse, Http404
 | 
					from django.http import HttpResponse, Http404
 | 
				
			||||||
@ -82,7 +84,7 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        stations.fetch()
 | 
					        stations.fetch()
 | 
				
			||||||
        return { 'stations': stations.stations }
 | 
					        return { 'stations': stations.stations }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get (self, request = None, **kwargs):
 | 
					    def get(self, request = None, **kwargs):
 | 
				
			||||||
        if not request.user.is_active:
 | 
					        if not request.user.is_active:
 | 
				
			||||||
            return Http404()
 | 
					            return Http404()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -90,7 +92,7 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        context = self.get_context_data(**kwargs)
 | 
					        context = self.get_context_data(**kwargs)
 | 
				
			||||||
        return render(request, self.template_name, context)
 | 
					        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:
 | 
					        if not request.user.is_active:
 | 
				
			||||||
            return Http404()
 | 
					            return Http404()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -121,6 +123,92 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        return HttpResponse('')
 | 
					        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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user