feat: store and update last_visit in person model
This commit is contained in:
48
app/base/migrations/0005_person_last_visit.py
Normal file
48
app/base/migrations/0005_person_last_visit.py
Normal 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),
|
||||||
|
]
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user