doorcontrol: Store cardholder_id->member per door for correct stats

This commit is contained in:
Adam Goldsmith 2024-02-09 11:59:52 -05:00
parent 7fd9181da1
commit 66b41e1448
4 changed files with 124 additions and 10 deletions

View File

@ -0,0 +1,56 @@
# Generated by Django 5.0.1 on 2024-02-09 16:37
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("doorcontrol", "0004_hidevent_is_red"),
("membershipworks", "0014_remove_eventext_details_timestamp"),
]
operations = [
migrations.CreateModel(
name="DoorCardholderMember",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("cardholder_id", models.IntegerField()),
(
"door",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="doorcontrol.door",
),
),
(
"member",
models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.CASCADE,
to="membershipworks.member",
),
),
],
),
migrations.AddConstraint(
model_name="doorcardholdermember",
constraint=models.UniqueConstraint(
fields=("door", "cardholder_id"), name="unique_door_cardholder_id"
),
),
migrations.AddConstraint(
model_name="doorcardholdermember",
constraint=models.UniqueConstraint(
fields=("door", "member"), name="unique_door_member"
),
),
]

View File

@ -2,10 +2,12 @@ from datetime import datetime
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models import F, Func, Q from django.db.models import F, Func, OuterRef, Q, Subquery
from django.db.models.functions import Mod from django.db.models.functions import Mod
from django.utils import timezone from django.utils import timezone
from membershipworks.models import Member
from .hid.DoorController import DoorController from .hid.DoorController import DoorController
@ -25,6 +27,25 @@ class Door(models.Model):
return self.name return self.name
class DoorCardholderMember(models.Model):
door = models.ForeignKey(Door, on_delete=models.CASCADE)
cardholder_id = models.IntegerField()
member = models.ForeignKey(Member, on_delete=models.CASCADE, db_constraint=False)
class Meta:
constraints = (
models.UniqueConstraint(
fields=("door", "cardholder_id"), name="unique_door_cardholder_id"
),
models.UniqueConstraint(
fields=("door", "member"), name="unique_door_member"
),
)
def __str__(self):
return f"{self.door} [{self.cardholder_id}]: {self.member}"
class HIDEventQuerySet(models.QuerySet): class HIDEventQuerySet(models.QuerySet):
def with_decoded_card_number(self): def with_decoded_card_number(self):
# TODO: CONV and BIT_COUNT are MySQL/MariaDB specific # TODO: CONV and BIT_COUNT are MySQL/MariaDB specific
@ -62,6 +83,15 @@ class HIDEventQuerySet(models.QuerySet):
) )
) )
def with_member_id(self):
return self.annotate(
member_id=Subquery(
DoorCardholderMember.objects.filter(
door=OuterRef("door"), cardholder_id=OuterRef("cardholder_id")
).values("member_id"),
)
)
class HIDEvent(models.Model): class HIDEvent(models.Model):
objects = HIDEventQuerySet.as_manager() objects = HIDEventQuerySet.as_manager()

View File

@ -6,11 +6,33 @@ from django.utils import timezone
from django_q.tasks import async_task from django_q.tasks import async_task
from cmsmanage.django_q2_helper import q_task_group from cmsmanage.django_q2_helper import q_task_group
from doorcontrol.models import Door, HIDEvent from doorcontrol.models import Door, DoorCardholderMember, HIDEvent
def get_cardholders(door: Door):
def make_ch_member(cardholder):
return DoorCardholderMember(
door=door,
cardholder_id=cardholder.attrib["cardholderID"],
member_id=cardholder.attrib.get("custom2"),
)
DoorCardholderMember.objects.bulk_create(
(
make_ch_member(cardholder)
for cardholder in door.controller.get_cardholders()
if "custom2" in cardholder.attrib
),
update_conflicts=True,
update_fields=("member",),
)
@transaction.atomic() @transaction.atomic()
def getMessages(door: Door): def getMessages(door: Door):
# TODO: this should in the cardholder syncing task
get_cardholders(door)
last_event = door.hidevent_set.order_by("timestamp").last() last_event = door.hidevent_set.order_by("timestamp").last()
if last_event is not None: if last_event is not None:
last_ts = timezone.make_naive(last_event.timestamp) last_ts = timezone.make_naive(last_event.timestamp)

View File

@ -13,6 +13,8 @@ from django.views.generic.list import ListView
import django_filters import django_filters
import django_tables2 as tables import django_tables2 as tables
from django_filters.views import BaseFilterView from django_filters.views import BaseFilterView
from django_mysql.models import GroupConcat
from django_mysql.models.functions import ConcatWS
from django_tables2 import SingleTableMixin from django_tables2 import SingleTableMixin
from django_tables2.export.views import ExportMixin from django_tables2.export.views import ExportMixin
@ -177,9 +179,10 @@ class AccessPerUnitTime(BaseAccessReport):
super() super()
.get_table_data() .get_table_data()
.filter(event_type__in=granted_event_types) .filter(event_type__in=granted_event_types)
.with_member_id()
.values(unit_time=Trunc("timestamp", unit_time)) .values(unit_time=Trunc("timestamp", unit_time))
.annotate( .annotate(
members=Count("cardholder_id", distinct=True), members=Count("member_id", distinct=True),
members_delta=( members_delta=(
F("members") F("members")
/ Window( / Window(
@ -242,10 +245,7 @@ class DeniedAccess(BaseAccessReport):
class MostActiveMembersTable(tables.Table): class MostActiveMembersTable(tables.Table):
cardholder_id = tables.Column() name = tables.Column()
name = tables.TemplateColumn(
"{{ record.forename|default:'' }} {{ record.surname|default:'' }}"
)
access_count = tables.Column() access_count = tables.Column()
@ -258,9 +258,15 @@ class MostActiveMembers(BaseAccessReport):
return ( return (
super() super()
.get_table_data() .get_table_data()
.values("cardholder_id", "forename", "surname") .with_member_id()
.order_by() .filter(member_id__isnull=False)
.annotate(access_count=Count("cardholder_id")) .values("member_id")
.annotate(
access_count=Count("member_id"),
name=GroupConcat(
ConcatWS("forename", "surname", separator=" "), distinct=True
),
)
.order_by("-access_count") .order_by("-access_count")
) )