Files
mkspc-mgmt/app/base/views/dashboard.py
2023-06-10 19:33:45 +02:00

125 lines
4.1 KiB
Python
Executable File

from django.db.models import Q, F, Sum, Count, ExpressionWrapper, IntegerField
from django.db.models.functions import Substr
from django.db.models.lookups import LessThan
from django.views.generic import TemplateView
from app.base.models import (
Booking, BookingType, CourseVisit, Person, Trait, TraitMapping
)
from app.base.views.login import LoginRequired
from app.base.views.model_views.base import ViewOptions
from datetime import date
from typing import Any
class DashboardOptions(ViewOptions, LoginRequired):
icon = 'chart-line'
title = 'Dashboard'
class DashboardView(DashboardOptions, TemplateView):
template_name = 'dashboard.html'
def get_context_data(self, **kwargs) -> 'dict[str, Any]':
context = super().get_context_data(**kwargs)
book_types = dict(BookingType.objects.values_list('key', 'label'))
trait_types = dict(Trait.objects.values_list('key', 'label'))
context['booking'] = {
'labels': book_types,
'by_type': stats_for_booking('type'),
}
context['trait'] = {
'labels': trait_types,
'by_type': stats_for_traits(),
}
context['person'] = {
'by_count': stats_for_person_count(),
}
context['by_month'] = stats_by_month()
context['head'] = {
'person': Person._meta.verbose_name_plural,
'bookings': Booking._meta.verbose_name_plural,
'booking_types': BookingType._meta.verbose_name_plural,
'traits': Trait._meta.verbose_name_plural,
}
return context
def stats_for_person_count():
today = date.today()
last_year = today.replace(year=today.year - 1)
stats = Person.objects.aggregate(
total=Count(1),
single_visit=Sum(LessThan(
# TODO: other DBs dont use microseconds
ExpressionWrapper(F('last_visit') - F('created'),
output_field=IntegerField()),
3 * 86_400_000_000)), # 3 days
long_not_seen=Sum(Q(last_visit__lt=last_year)),
)
stats['no_booking'] = stats['total'] - Booking.objects.aggregate(
c=Count('user', distinct=True))['c']
stats['no_course'] = stats['total'] - CourseVisit.objects.aggregate(
c=Count('participant', distinct=True))['c']
return stats
def stats_by_month():
stats = {}
for stat in stats_for_person_by_month():
year, month = stat['month'].split('-')
if year not in stats:
stats[year] = [[0, 0, 0] for x in range(13)]
stats[year][0][2] += stat['count']
stats[year][int(month)][2] += stat['count']
for stat in stats_for_booking('month'):
year, month = stat['month'].split('-')
if year not in stats:
stats[year] = [[0, 0, 0] for x in range(13)]
stats[year][0][0] += stat['sum']
stats[year][0][1] += stat['count']
stats[year][int(month)][0] += stat['sum']
stats[year][int(month)][1] += stat['count']
return sorted(stats.items(), reverse=True)
def stats_for_person_by_month():
return Person.objects.values(
month=Substr('created', 1, 7), # YYYY-MM
).annotate(count=Count(1)).order_by('month')
def stats_for_booking(groupby: str):
_Q = Booking.objects.filter(Q(end_time__isnull=False)).annotate(
# TODO: other DBs dont use microseconds
diff=ExpressionWrapper(
(F('end_time') - F('begin_time')) / 60_000_000,
output_field=IntegerField()),
).filter(diff__lt=1440) # filter 24+ hours. Probably wrong checkout
if groupby == 'month':
_Q = _Q.annotate(month=Substr('begin_time', 1, 7)) # YYYY-MM
return _Q.values(groupby).annotate(
sum=Sum('diff', output_field=IntegerField()),
count=Count(1),
).values(groupby, 'sum', 'count').order_by(groupby)
def stats_for_traits():
return TraitMapping.objects.filter(
Q(valid_from__lte=date.today()),
Q(valid_until=None) | Q(valid_until__gte=date.today()),
).values('trait').annotate(
count=Count(1),
).values('trait', 'count').order_by('trait')