feat: store and update last_visit in person model

This commit is contained in:
relikd
2023-06-08 13:26:46 +02:00
parent 06a09bafd9
commit cb13906c8a
5 changed files with 141 additions and 28 deletions

View File

@@ -0,0 +1,48 @@
# Generated by Django 4.2 on 2023-06-08 08:07
from django.db import migrations
from app.base.forms.fields import DateField
from datetime import date
def calc_last_visit(apps, schema_editor):
Person = apps.get_model('base', 'Person')
Booking = apps.get_model('base', 'Booking')
CourseVisit = apps.get_model('base', 'CourseVisit')
max_dates = {}
zero = date.fromtimestamp(0)
for (pk, day) in Booking.objects.values_list('user', 'begin_time__date'):
max_dates[pk] = max(day, max_dates.get(pk, zero))
for (pk1, pk2, day) in CourseVisit.objects.values_list(
'participant', 'teacher', 'date'):
max_dates[pk1] = max(day, max_dates.get(pk1, zero))
max_dates[pk2] = max(day, max_dates.get(pk2, zero))
for person in Person.objects.all():
person.last_visit = max(person.created, max_dates.get(person.pk, zero))
person.save()
def noop(apps, schema_editor):
pass # allow reverse migrate because field will be just deleted
class Migration(migrations.Migration):
dependencies = [
('base', '0004_person_created_merge_street'),
]
operations = [
migrations.AddField(
model_name='person',
name='last_visit',
field=DateField(verbose_name='Letzter Besuch', editable=False,
default=date.fromtimestamp(0)),
preserve_default=False,
),
migrations.RunPython(calc_last_visit, noop),
]

View File

@@ -41,6 +41,26 @@ class Booking(models.Model):
to_current_timezone(self.end_time).strftime('%H:%M') to_current_timezone(self.end_time).strftime('%H:%M')
if self.end_time else '') if self.end_time else '')
def save(self, *args, **kwargs):
# update last_visit time of all involved persons
prev = Booking.objects.get(pk=self.pk) if self.pk else None
rv = super().save(*args, **kwargs)
if prev and prev.user != self.user:
prev.user.update_last_visit(None)
if not prev or prev.user != self.user or \
prev.begin_time.date() != self.begin_time.date():
self.user.update_last_visit(self.begin_time.date())
return rv
def delete(self, *args, **kwargs):
rv = super().delete(*args, **kwargs)
# update last_visit time for person
self.user.update_last_visit(None)
return rv
@property @property
def duration(self) -> 'int|None': def duration(self) -> 'int|None':
if self.end_time: if self.end_time:
@@ -54,11 +74,6 @@ class Booking(models.Model):
traits = set(x[0] for x in traits) traits = set(x[0] for x in traits)
return self.type.price_with_traits(self.duration or 0, traits) return self.type.price_with_traits(self.duration or 0, traits)
@staticmethod
def latest_checkin_query(for_user: 'Person|OuterRef'):
objects = Booking.objects.filter(user=for_user)
return objects.order_by('-begin_time').values('begin_time')[:1]
@staticmethod @staticmethod
def currently_open_checkin(for_user: 'Person|OuterRef') -> 'Booking|None': def currently_open_checkin(for_user: 'Person|OuterRef') -> 'Booking|None':
return Booking.objects.filter( return Booking.objects.filter(

View File

@@ -33,3 +33,39 @@ class CourseVisit(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('course-visit:detail', kwargs={'pk': self.pk}) return reverse('course-visit:detail', kwargs={'pk': self.pk})
def save(self, *args, **kwargs):
# update last_visit time of all involved persons
old_date: 'date|None' = None
old_list: 'list[Person]' = []
if self.pk:
prev = CourseVisit.objects.get(pk=self.pk)
old_date = prev.date
old_list.append(prev.participant)
if prev.teacher:
old_list.append(prev.teacher)
rv = super().save(*args, **kwargs)
new_date = self.date
new_list: 'list[Person]' = [self.participant]
if self.teacher:
new_list.append(self.teacher)
for person in old_list:
if person not in new_list:
person.update_last_visit(None)
for person in new_list:
if person not in old_list or old_date != new_date:
person.update_last_visit(new_date)
return rv
def delete(self, *args, **kwargs):
rv = super().delete(*args, **kwargs)
# update last_visit time of all involved persons
self.participant.update_last_visit(None)
if self.teacher:
self.teacher.update_last_visit(None)
return rv

View File

@@ -3,17 +3,20 @@ from django.db.models import Q
from django.urls import reverse from django.urls import reverse
from app.base.forms.fields import DateField from app.base.forms.fields import DateField
from app.base.models.course import Course
from app.base.models.booking import Booking from app.base.models.booking import Booking
from app.base.models.course import Course
from app.base.models.course_visit import CourseVisit
from datetime import datetime, date from datetime import datetime, date
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from app.base.models import Account, Note, CourseVisit, TraitMapping from app.base.models import Account, Note, TraitMapping
class Person(models.Model): class Person(models.Model):
created: 'models.DateField[date]' = DateField('Angelegt', editable=False) created: 'models.DateField[date]' = DateField('Angelegt', editable=False)
last_visit: 'models.DateField[date]' = DateField(
'Letzter Besuch', editable=False)
uuid = models.CharField('Karten-ID', max_length=200, blank=True) uuid = models.CharField('Karten-ID', max_length=200, blank=True)
first_name = models.CharField('Vorname', max_length=200) first_name = models.CharField('Vorname', max_length=200)
@@ -55,6 +58,7 @@ class Person(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.pk: if not self.pk:
self.created = date.today() self.created = date.today()
self.last_visit = date.today()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@property @property
@@ -76,15 +80,32 @@ class Person(models.Model):
return self.traits_at_date(datetime.now()).values_list( return self.traits_at_date(datetime.now()).values_list(
'pk', 'trait__key', 'trait__label') 'pk', 'trait__key', 'trait__label')
@property
def current_checkin(self):
return Booking.currently_open_checkin(self)
def traits_at_date(self, date: datetime): def traits_at_date(self, date: datetime):
return self.traits.filter( return self.traits.filter(
Q(valid_from__lte=date), Q(valid_from__lte=date),
Q(valid_until__gte=date) | Q(valid_until=None)) Q(valid_until__gte=date) | Q(valid_until=None))
def last_check_in(self) -> 'datetime|None': def lookup_last_visit(self) -> date:
obj = Booking.latest_checkin_query(self).first() ''' Return newest date from either booking, course, or created. '''
return obj['begin_time'] if obj else None last_booking = Booking.objects.filter(
user=self).order_by('-begin_time').first()
last_course = CourseVisit.objects.filter(
Q(participant=self) | Q(teacher=self)).order_by('-date').first()
available_dates = [self.created]
if last_booking:
available_dates.append(last_booking.begin_time.date())
if last_course:
available_dates.append(last_course.date)
return max(available_dates)
@property def update_last_visit(self, new_visit: 'date|None') -> None:
def current_checkin(self): if self.last_visit == new_visit:
return Booking.currently_open_checkin(self) return # no need to update
if not new_visit or new_visit < self.last_visit:
new_visit = self.lookup_last_visit()
self.last_visit = new_visit
self.save()

View File

@@ -1,9 +1,6 @@
from django.db.models import OuterRef, Subquery
from django.urls import path from django.urls import path
from app.base.models.booking import Booking from app.base.models import Booking, Person, Transaction
from app.base.models.transaction import Transaction
from app.base.models.person import Person
from app.base.views.login import LoginRequired from app.base.views.login import LoginRequired
from app.base.views.model_views.base import ( from app.base.views.model_views.base import (
ModelDetailView, ModelListView, ModelCreateView, ModelUpdateView, ModelDetailView, ModelListView, ModelCreateView, ModelUpdateView,
@@ -37,30 +34,26 @@ class PersonOptions(ViewOptions[Person], LoginRequired):
'trait': 'traits__pk', 'trait': 'traits__pk',
'course': 'courses__pk', 'course': 'courses__pk',
} }
list_columns = ['display_name', 'birth_date', 'last_check_in'] list_columns = ['display_name', 'birth_date', 'last_visit']
list_render = { list_render = {
'display_name': {'verbose_name': 'Nutzer:in'}, 'display_name': {
'verbose_name': 'Nutzer:in'
},
'birth_date': { 'birth_date': {
'verbose_name': 'Geburtsjahr', 'verbose_name': 'Geburtsjahr',
'date_format': 'Y', 'date_format': 'Y',
}, },
'last_check_in': { 'last_visit': {
'verbose_name': 'Letzter Besuch', 'date_format': 'D. d. M y',
'date_format': 'D. d. M y, H:i',
}, },
} }
class PersonListView(PersonOptions, ModelListView): class PersonListView(PersonOptions, ModelListView):
icon = 'users' icon = 'users'
ordering = ('-last_check_in',) ordering = ('-last_visit',)
search_fields = ['uuid', 'first_name', 'last_name'] search_fields = ['uuid', 'first_name', 'last_name']
def get_queryset(self):
query = Subquery(Booking.latest_checkin_query(OuterRef('pk')))
rv = Person.objects.annotate(last_check_in=query)
return rv.order_by(*self.ordering)
class PersonCreateView(PersonOptions, ModelCreateView): class PersonCreateView(PersonOptions, ModelCreateView):
# on_success = 'person:detail', '{.pk}' # the default anyway # on_success = 'person:detail', '{.pk}' # the default anyway