doorcontrol: Add report for assigned NFC cards
This commit is contained in:
parent
68a917f3f8
commit
c8b3edcacf
@ -18,12 +18,13 @@ class DoorControlDashboardFragment(dashboard.LinksCardDashboardFragment):
|
||||
for name, link in report._report_types()
|
||||
] + [
|
||||
Link(
|
||||
"Assign NFC Card ",
|
||||
"Assign NFC Card",
|
||||
reverse("doorcontrol:assign-nfc-card-user-selector"),
|
||||
permission="doorcontrol.assign_nfc_card",
|
||||
)
|
||||
),
|
||||
Link(
|
||||
"Assigned NFC Cards",
|
||||
reverse("doorcontrol:assigned-nfc-cards"),
|
||||
permission="doorcontrol.assign_nfc_card",
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
def visible(self) -> bool:
|
||||
return self.request.user.has_perm("doorcontrol.view_hidevent")
|
||||
|
@ -39,3 +39,14 @@ class AttributeScheduleRuleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = AttributeScheduleRule
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class AssignedNfcCardsReportFilters(forms.Form):
|
||||
has_mw_nfc_card = forms.NullBooleanField(
|
||||
label="Has NFC card in MembershipWorks",
|
||||
widget=forms.Select(choices=[("any", "Any"), (True, "Yes"), (False, "No")]),
|
||||
)
|
||||
has_access_nfc_card = forms.NullBooleanField(
|
||||
label="Has NFC card in UniFi Access",
|
||||
widget=forms.Select(choices=[("any", "Any"), (True, "Yes"), (False, "No")]),
|
||||
)
|
||||
|
@ -63,3 +63,25 @@ class BusiestTimeOfDayTable(tables.Table):
|
||||
timestamp__hour = tables.TemplateColumn("{{ value }}:00", verbose_name="Hour")
|
||||
events = tables.Column()
|
||||
members = tables.Column()
|
||||
|
||||
|
||||
class AssignedNfcCardsTable(tables.Table):
|
||||
member__account_name = tables.Column("Name")
|
||||
member__nfc_card_number = tables.Column("MW NFC Card")
|
||||
access_user__nfc_cards = tables.TemplateColumn(
|
||||
"""
|
||||
{% if value %}
|
||||
<ul>
|
||||
{% for card in value %}
|
||||
<li>{{ card.type }}: {{ card.id }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
???
|
||||
{% endif %}
|
||||
""",
|
||||
empty_values=[[]],
|
||||
verbose_name="UniFi Access Cards",
|
||||
)
|
||||
member__access_wood_shop = tables.BooleanColumn(verbose_name="Access Wood Shop")
|
||||
member__access_metal_shop = tables.BooleanColumn(verbose_name="Access Metal Shop")
|
||||
|
@ -5,7 +5,7 @@ from django.conf import settings
|
||||
from django.db.models import Q
|
||||
|
||||
from unifi_access import AccessClient
|
||||
from unifi_access.schemas import AccessPolicy, DoorResource, UserStatus
|
||||
from unifi_access.schemas import AccessPolicy, DoorResource, FullUser, UserStatus
|
||||
from unifi_access.schemas import User as AccessUser
|
||||
|
||||
from cmsmanage.django_q2_helper import q_task_group
|
||||
@ -172,3 +172,11 @@ def update_access():
|
||||
)
|
||||
|
||||
sync_members(access_client)
|
||||
|
||||
|
||||
@q_task_group("Update Access Users")
|
||||
def update_access_users() -> list[FullUser]:
|
||||
access_client = AccessClient(
|
||||
settings.UNIFI_ACCESS_HOST, settings.UNIFI_ACCESS_API_TOKEN, verify=False
|
||||
)
|
||||
return list(access_client.fetch_all_users__unpaged())
|
||||
|
@ -0,0 +1,40 @@
|
||||
{% extends "base.dj.html" %}
|
||||
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block title %}Assigned NFC Cards{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="vstack align-items-center">
|
||||
<form hx-get="{{ request.path }}{% querystring has_mw_nfc_card=None has_access_nfc_card=None refresh=None %}"
|
||||
hx-include="this"
|
||||
hx-target="body"
|
||||
hx-push-url="true"
|
||||
hx-trigger="change">
|
||||
<div class="row justify-content-center d-print-none">
|
||||
{% for field in form %}
|
||||
<div class="col-12 col-sm-3 mb-2">
|
||||
{{ field.label_tag }} {% render_field field class+="form-select form-select-sm" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
type="button"
|
||||
hx-get="{{ request.path }}{% querystring has_mw_nfc_card=None has_access_nfc_card=None refresh=None %}"
|
||||
hx-push-url="false"
|
||||
hx-indicator="find .htmx-indicator"
|
||||
hx-vals='{"refresh": true}'>
|
||||
<span class="spinner-border spinner-border-sm htmx-indicator"
|
||||
aria-hidden="true"></span>
|
||||
Refresh Access Data
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">{% include "cmsmanage/components/download_table.dj.html" %}</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% render_table table %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -15,4 +15,9 @@ urlpatterns = [report._urlpattern() for report in views.REPORTS] + [
|
||||
views.AssignNfcCardView.as_view(),
|
||||
name="assign-nfc-card",
|
||||
),
|
||||
path(
|
||||
"assigned-nfc-cards/",
|
||||
views.AssignedNfcCardsReport.as_view(),
|
||||
name="assigned-nfc-cards",
|
||||
),
|
||||
]
|
||||
|
@ -20,6 +20,7 @@ import django_filters
|
||||
import django_q.tasks as q2_tasks
|
||||
import django_tables2 as tables
|
||||
from django_filters.views import BaseFilterView
|
||||
from django_q.signing import BadSignature
|
||||
from django_tables2 import SingleTableMixin
|
||||
from django_tables2.export.views import ExportMixin
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
@ -34,8 +35,13 @@ from unifi_access.schemas import (
|
||||
UserStatus,
|
||||
)
|
||||
|
||||
from doorcontrol.forms import AssignedNfcCardsReportFilters
|
||||
from doorcontrol.tasks.update_unifi_access import update_access_users
|
||||
from membershipworks.models import Member
|
||||
|
||||
from .models import Door, HIDEvent
|
||||
from .tables import (
|
||||
AssignedNfcCardsTable,
|
||||
BusiestDayOfWeekTable,
|
||||
BusiestTimeOfDayTable,
|
||||
DeniedAccessTable,
|
||||
@ -314,39 +320,36 @@ class BusiestTimeOfDay(BaseAccessReport):
|
||||
)
|
||||
|
||||
|
||||
def update_access_users() -> list[FullUser]:
|
||||
access_client = AccessClient(
|
||||
settings.UNIFI_ACCESS_HOST, settings.UNIFI_ACCESS_API_TOKEN, verify=False
|
||||
)
|
||||
return list(access_client.fetch_all_users__unpaged())
|
||||
def fetch_access_users_results(force_refresh: bool = False) -> list[FullUser] | None:
|
||||
task_group = update_access_users.q_task_group
|
||||
|
||||
try:
|
||||
if force_refresh:
|
||||
q2_tasks.delete_group(task_group)
|
||||
refresh_task_id = q2_tasks.async_task(
|
||||
update_access_users, group=task_group, cached=5 * 60
|
||||
)
|
||||
return q2_tasks.result(refresh_task_id, wait=-1, cached=True)
|
||||
|
||||
update_users_results = q2_tasks.result_group(task_group, cached=True)
|
||||
if update_users_results and len(update_users_results) > 0:
|
||||
return update_users_results[0]
|
||||
# TODO: this could be better
|
||||
except BadSignature:
|
||||
return None
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("doorcontrol.assign_nfc_card", raise_exception=True)
|
||||
def assign_nfc_card_user_selector(request: HttpRequest):
|
||||
template_name = "doorcontrol/assign_nfc_card_user_selector.dj.html"
|
||||
task_group = "update_access_users"
|
||||
|
||||
all_users: list[FullUser] | None = None
|
||||
refresh_task_id = None
|
||||
update_users_results = q2_tasks.result_group(task_group, cached=True)
|
||||
if (
|
||||
update_users_results
|
||||
and len(update_users_results) > 0
|
||||
and not request.POST.get("force_refresh")
|
||||
):
|
||||
all_users = update_users_results[0]
|
||||
else:
|
||||
q2_tasks.delete_group(task_group)
|
||||
refresh_task_id = q2_tasks.async_task(
|
||||
update_access_users, group=task_group, cached=5 * 60
|
||||
)
|
||||
|
||||
filtered_users = []
|
||||
|
||||
if request.method == "POST":
|
||||
if refresh_task_id:
|
||||
all_users = q2_tasks.result(refresh_task_id, wait=-1, cached=True)
|
||||
all_users = fetch_access_users_results(
|
||||
request.POST.get("force_refresh") == "true"
|
||||
)
|
||||
|
||||
template_name += "#results"
|
||||
all_filtered_users = (
|
||||
@ -514,3 +517,56 @@ class AssignNfcCardView(PermissionRequiredMixin, TemplateView):
|
||||
).session_id
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class AssignedNfcCardsReport(
|
||||
ExportMixin, SingleTableMixin, PermissionRequiredMixin, TemplateView
|
||||
):
|
||||
permission_required = "doorcontrol.assign_nfc_card"
|
||||
template_name = "doorcontrol/assigned_nfc_cards_report.dj.html"
|
||||
table_class = AssignedNfcCardsTable
|
||||
export_formats = ("csv", "xlsx", "ods")
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
if "form" not in kwargs:
|
||||
kwargs["form"] = AssignedNfcCardsReportFilters(self.request.GET)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_table_data(self):
|
||||
access_users = fetch_access_users_results(
|
||||
force_refresh=("refresh" in self.request.GET)
|
||||
)
|
||||
if access_users:
|
||||
access_users_by_employee_number = {
|
||||
user.employee_number: user for user in access_users
|
||||
}
|
||||
else:
|
||||
access_users_by_employee_number = {}
|
||||
|
||||
form = AssignedNfcCardsReportFilters(self.request.GET)
|
||||
|
||||
def get_filtered_members():
|
||||
members = Member.objects.with_is_active().filter(is_active=True)
|
||||
if form.is_valid() and form.cleaned_data["has_mw_nfc_card"] is not None:
|
||||
members = members.alias(
|
||||
has_nfc_card_number=(
|
||||
Q(nfc_card_number__isnull=False) & ~Q(nfc_card_number="")
|
||||
)
|
||||
).filter(has_nfc_card_number=form.cleaned_data["has_mw_nfc_card"])
|
||||
|
||||
for member in members.all():
|
||||
access_user = access_users_by_employee_number.get(member.uid, None)
|
||||
if (
|
||||
form.is_valid()
|
||||
and form.cleaned_data["has_access_nfc_card"] is not None
|
||||
and access_user
|
||||
and (
|
||||
bool(access_user.nfc_cards)
|
||||
!= form.cleaned_data["has_access_nfc_card"]
|
||||
)
|
||||
):
|
||||
continue
|
||||
|
||||
yield {"member": member, "access_user": access_user}
|
||||
|
||||
return list(get_filtered_members())
|
||||
|
Loading…
x
Reference in New Issue
Block a user