imrpove statistics rendering + filters

This commit is contained in:
bkfox 2024-04-12 16:36:05 +02:00
parent 0ee72f30c5
commit 8a6f72ca83
5 changed files with 112 additions and 76 deletions

View File

@ -33,6 +33,9 @@ class LogQuerySet(models.QuerySet):
def after(self, date):
return self.filter(date__gte=date) if isinstance(date, tz.datetime) else self.filter(date__date__gte=date)
def before(self, date):
return self.filter(date__lte=date) if isinstance(date, tz.datetime) else self.filter(date__date__lte=date)
def on_air(self):
return self.filter(type=Log.TYPE_ON_AIR)

View File

@ -6,15 +6,20 @@
{% block content-container %}
<div class="container">
{# TODO: date subtitle #}
{% comment %}
<nav class="navbar" role="menu">
{% with "admin:tools-stats" as url_name %}
{% include "aircox/widgets/dates_menu.html" %}
{% endwith %}
</nav>
{% endcomment %}
<form method="GET" class="box mt-3 mb-3">
<h3 class="title is-3">{% translate "Filter by date" %}</h3>
<div class="flex-row gap-3" style="align-items: flex-end">
<div class="field mb-0">
<label class="label">{% translate "from" %}</label>
<input type="date" class="input" name="min_date" value="{{ min_date|date:"Y-m-d" }}"/>
</div>
<div class="field mb-0">
<label class="label">{% translate "... to" %}</label>
<input type="date" class="input" name="max_date" value="{{ max_date|date:"Y-m-d" }}"/>
</div>
<button class="button">{% translate "Apply" %}</button>
</div>
</form>
<a-statistics class="column">
<template v-slot="{counts}">
<table class="table is-hoverable is-fullwidth">
@ -26,49 +31,58 @@
</tr>
</thead>
<tbody>
{% for object in object_list %}
{% with object|is_diffusion as is_diff %}
{% if is_diff %}
<tr class="bg-main">
<td>{{ object.start|time:"H:i" }} - {{ object.end|time:"H:i" }}</td>
<td colspan="2">
<a href="{% url "episode-detail" slug=object.episode.slug %}" target="new">{{ object.episode|default:"" }}</a>
</td>
</tr>
{% endif %}
{% with object|get_tracks as tracks %}
{% for track in tracks %}
<tr {% if is_diff %}class="bg-main-light"{% endif %}>
{% if forloop.first %}
<td rowspan="{{ tracks|length }}">{{ object.start|time:"H:i" }} {% if object|is_diffusion %} - {{ object.end|time:"H:i" }}{% endif %}</td>
{% endif %}
<td>
{% if object.source %}{{ object.source }} / {% endif %}
{% include "aircox/widgets/track_item.html" with object=track %}
</td>
{% with track.tags.all|join:', ' as tags %}
<td>
{% if tags and tags.strip %}
<label class="checkbox">
<input type="checkbox" checked value="{{ tags|escape }}" name="data">
{{ tags }}
</label>
{% endif %}
</td>
{% endwith %}
{% regroup object_list by date.date as by_date %}
{% for date, objects in by_date %}
<tr>
<th colspan="3">
{{ date|date:"l - d F Y" }}
</th>
</tr>
{% empty %}
{% for object in objects %}
{% with object|is_diffusion as is_diff %}
{% if is_diff %}
<tr class="bg-main-light">
<td colspan="3">{% translate "No tracks" %}</td>
<tr class="bg-main">
<td>{{ object.start|time:"H:i" }} - {{ object.end|time:"H:i" }}</td>
<td colspan="2">
<a href="{% url "episode-detail" slug=object.episode.slug %}" target="new">{{ object.episode|default:"" }}</a>
</td>
</tr>
{% endif %}
{% endfor %}
{% endwith %}
{% endwith %}
{% with object|get_tracks as tracks %}
{% for track in tracks %}
<tr {% if is_diff %}class="bg-main-light"{% endif %}>
{% if forloop.first %}
<td rowspan="{{ tracks|length }}">{{ object.start|time:"H:i" }} {% if object|is_diffusion %} - {{ object.end|time:"H:i" }}{% endif %}</td>
{% endif %}
<td>
{% if object.source %}{{ object.source }} / {% endif %}
{% include "aircox/widgets/track_item.html" with object=track %}
</td>
{% with track.tags.all|join:', ' as tags %}
<td>
{% if tags and tags.strip %}
<label class="checkbox">
<input type="checkbox" checked value="{{ tags|escape }}" name="data">
{{ tags }}
</label>
{% endif %}
</td>
{% endwith %}
</tr>
{% empty %}
{% if is_diff %}
<tr class="bg-main-light">
<td colspan="3">{% translate "No tracks" %}</td>
</tr>
{% endif %}
{% endfor %}
{% endwith %}
{% endwith %}
{% endfor %}
{% endfor %}
</tbody>
<tfoot>

View File

@ -42,7 +42,7 @@ class DashboardView(DashboardBaseView, TemplateView):
class StatisticsView(DashboardBaseView, LogListView):
template_name = "aircox/dashboard/statistics.html"
date = None
redirect_date_url = "dashboard-statistics"
# redirect_date_url = "dashboard-statistics"
# TOOD: test_func & perms check

View File

@ -17,35 +17,32 @@ __all__ = ("LogListMixin", "LogListView", "LogListAPIView")
class LogListMixin(GetDateMixin):
model = Log
min_date = None
max_date = None
def get_date(self):
date = super().get_date()
def get_date(self, param):
date = super().get_date(param)
if date is not None and not self.request.user.is_staff:
return min(date, datetime.date.today())
return date
def filter_qs(self, query):
if self.min_date:
query = query.after(self.min_date)
if self.max_date:
query = query.before(self.max_date)
if not self.min_date and not self.max_date and self.date:
return query.date(self.date)
return query
def get_queryset(self):
# only get logs for tracks: log for diffusion will be retrieved
# by the diffusions' queryset.
qs = super().get_queryset().on_air().filter(track__isnull=False).filter(date__lte=tz.now())
return (
qs.date(self.date)
if self.date is not None
else qs.after(self.min_date)
if self.min_date is not None
else qs
)
query = super().get_queryset().on_air().filter(track__isnull=False).filter(date__lte=tz.now())
return self.filter_qs(query)
def get_diffusions_queryset(self):
qs = Diffusion.objects.station(self.station).on_air().filter(start__lte=tz.now()).before()
return (
qs.date(self.date)
if self.date is not None
else qs.after(self.min_date)
if self.min_date is not None
else qs
)
query = Diffusion.objects.station(self.station).on_air().filter(start__lte=tz.now()).before()
return self.filter_qs(query)
def get_object_list(self, logs, full=False):
"""Return diffusions merged to the provided logs iterable.
@ -55,7 +52,6 @@ class LogListMixin(GetDateMixin):
diffs = self.get_diffusions_queryset()
if self.request.user.is_staff and full:
return sorted(list(logs) + list(diffs), key=lambda obj: obj.start)
print(">>>>", len(logs), len(diffs), Log.merge_diffusions(logs, diffs))
return Log.merge_diffusions(logs, diffs)
@ -64,11 +60,31 @@ class LogListView(AttachedToMixin, BaseView, LogListMixin, ListView):
`request.GET`, defaults to today)."""
redirect_date_url = "log-list"
date_delta = tz.timedelta(days=7)
def get_date(self):
date = super().get_date()
def get_date(self, param):
date = super().get_date(param)
return datetime.date.today() if date is None else date
def get(self, request, **kwargs):
min_date = self.get_date("min_date")
max_date = self.get_date("max_date")
# ensure right values for min and max
min_date, max_date = min(min_date, max_date), max(min_date, max_date)
# limit logs list size using date delta
if min_date and max_date:
max_date = min(min_date + self.date_delta, max_date)
elif min_date:
max_date = min_date + self.date_delta
elif max_date:
min_date = max_date - self.date_delta
self.min_date = min_date
self.max_date = max_date
return super().get(request, **kwargs)
def get_context_data(self, **kwargs):
today = datetime.date.today()
# `super()...` must be called before updating kwargs, in order
@ -76,6 +92,9 @@ class LogListView(AttachedToMixin, BaseView, LogListMixin, ListView):
kwargs = super().get_context_data(**kwargs)
kwargs.update(
{
"min_date": self.min_date or today,
"max_date": self.max_date or today,
"today": datetime.date.today(),
"date": self.date,
"dates": (today - datetime.timedelta(days=i) for i in range(0, 7)),
"object_list": self.get_object_list(self.object_list),
@ -101,8 +120,8 @@ class LogListAPIView(LogListMixin, BaseAPIView, ListAPIView):
def list(self, *args, **kwargs):
return super().list(*args, **kwargs)
def get_date(self):
date = super().get_date()
def get_date(self, param):
date = super().get_date(param)
if date is None:
self.min_date = tz.now() - tz.timedelta(minutes=30)
return date

View File

@ -12,9 +12,9 @@ class GetDateMixin:
date = None
redirect_date_url = None
def get_date(self):
date = self.request.GET.get("date")
return str_to_date(date, "-") if date is not None else self.kwargs["date"] if "date" in self.kwargs else None
def get_date(self, param):
date = self.request.GET.get(param)
return str_to_date(date, "-") if date else self.kwargs[param] if param in self.kwargs else None
def get(self, *args, **kwargs):
if self.redirect_date_url and self.request.GET.get("date"):
@ -23,7 +23,7 @@ class GetDateMixin:
date=self.request.GET["date"].replace("-", "/"),
)
self.date = self.get_date()
self.date = self.get_date("date")
return super().get(*args, **kwargs)