+ {% for year, stat in by_month %}
{{ year }} |
∑ | Jan | Feb | Mär | Apr | Mai | Jun | Jul | Aug | Sep | Okt | Nov | Dez |
+
+ | Personen |
+ {% for sum, count, people in stat %}
+
+ {% if people and not forloop.first %}
+ {{ people }}
+ {% else %}
+ {{ people }}
+ {% endif %}
+ |
+ {% endfor %}
+
| Buchungen |
- {% for sum, count in stat %}{{ count }} | {% endfor %}
+ {% for sum, count, people in stat %}
+ {{ count }} |
+ {% endfor %}
| Minuten (ø) |
- {% for sum, count in stat %}{{ sum|divide:count|floatformat:1 }} | {% endfor %}
+ {% for sum, count, people in stat %}
+ {{ sum|divide:count|floatformat:1 }} |
+ {% endfor %}
| Minuten |
- {% for sum, count in stat %}{{ sum }} | {% endfor %}
+ {% for sum, count, people in stat %}
+ {{ sum }} |
+ {% endfor %}
{% endfor %}
diff --git a/app/base/views/dashboard.py b/app/base/views/dashboard.py
index 10ae118..4764813 100755
--- a/app/base/views/dashboard.py
+++ b/app/base/views/dashboard.py
@@ -1,8 +1,11 @@
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, Trait, TraitMapping
+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
@@ -23,30 +26,20 @@ class DashboardView(DashboardOptions, TemplateView):
book_types = dict(BookingType.objects.values_list('key', 'label'))
trait_types = dict(Trait.objects.values_list('key', 'label'))
- # Booking stats
-
- book_by_type = stats_for_booking('type')
- book_by_month = {}
-
- for stat in stats_for_booking('month'):
- year, month = stat['month'].split('-')
- if year not in book_by_month:
- book_by_month[year] = [[0, 0] for x in range(13)]
- book_by_month[year][0][0] += stat['sum']
- book_by_month[year][0][1] += stat['count']
- book_by_month[year][int(month)][0] += stat['sum']
- book_by_month[year][int(month)][1] += stat['count']
-
context['booking'] = {
'labels': book_types,
- 'by_type': book_by_type,
- 'by_month': book_by_month,
+ '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,
@@ -55,9 +48,59 @@ class DashboardView(DashboardOptions, TemplateView):
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(
- # for whatever reason django uses nano seconds
+ # TODO: other DBs dont use microseconds
diff=ExpressionWrapper(
(F('end_time') - F('begin_time')) / 60_000_000,
output_field=IntegerField()),
diff --git a/app/base/views/model_views/person.py b/app/base/views/model_views/person.py
index 11931eb..37edb5a 100755
--- a/app/base/views/model_views/person.py
+++ b/app/base/views/model_views/person.py
@@ -33,6 +33,9 @@ class PersonOptions(ViewOptions[Person], LoginRequired):
list_filter = {
'trait': 'traits__pk',
'course': 'courses__pk',
+ 'created': 'created',
+ 'created.y': 'created__year',
+ 'created.m': 'created__month',
}
list_columns = ['display_name', 'birth_date', 'last_visit']
list_render = {