Add doorcontrol app for managing HID door controllers
Just read-only access to the event log dump for now
This commit is contained in:
parent
12d5c9cba5
commit
a6c531c22f
@ -39,6 +39,7 @@ INSTALLED_APPS = [
|
|||||||
"rentals.apps.RentalsConfig",
|
"rentals.apps.RentalsConfig",
|
||||||
"membershipworks.apps.MembershipworksConfig",
|
"membershipworks.apps.MembershipworksConfig",
|
||||||
"paperwork.apps.PaperworkConfig",
|
"paperwork.apps.PaperworkConfig",
|
||||||
|
"doorcontrol.apps.DoorControlConfig",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@ -75,6 +76,7 @@ WSGI_APPLICATION = "cmsmanage.wsgi.application"
|
|||||||
|
|
||||||
DATABASE_ROUTERS = [
|
DATABASE_ROUTERS = [
|
||||||
"membershipworks.routers.MembershipWorksRouter",
|
"membershipworks.routers.MembershipWorksRouter",
|
||||||
|
"doorcontrol.routers.DoorControlRouter",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default URL to redirect to after authentication
|
# Default URL to redirect to after authentication
|
||||||
|
0
doorcontrol/__init__.py
Normal file
0
doorcontrol/__init__.py
Normal file
19
doorcontrol/admin.py
Normal file
19
doorcontrol/admin.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import HIDEvent
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(HIDEvent)
|
||||||
|
class HIDEventAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ["description", "forename", "surname", "cardholder_id"]
|
||||||
|
list_display = ["door_name", "timestamp", "event_type", "description", "is_red"]
|
||||||
|
list_filter = ["door_name", "event_type"]
|
||||||
|
|
||||||
|
def has_add_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
return False
|
7
doorcontrol/apps.py
Normal file
7
doorcontrol/apps.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DoorControlConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "doorcontrol"
|
||||||
|
verbose_name = "Door Control"
|
99
doorcontrol/migrations/0001_initial.py
Normal file
99
doorcontrol/migrations/0001_initial.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Generated by Django 4.1.3 on 2023-01-25 02:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="HIDEvent",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("door_name", models.CharField(db_column="doorName", max_length=64)),
|
||||||
|
("timestamp", models.DateTimeField()),
|
||||||
|
(
|
||||||
|
"event_type",
|
||||||
|
models.IntegerField(
|
||||||
|
choices=[
|
||||||
|
(1022, "Denied Access: Card Not Found"),
|
||||||
|
(1023, "Denied Access Access: PIN Not Found"),
|
||||||
|
(2020, "Granted Access"),
|
||||||
|
(2021, "Granted Access: Extended Time"),
|
||||||
|
(2024, "Denied Access: Schedule"),
|
||||||
|
(2029, "Denied Access: Wrong PIN"),
|
||||||
|
(2036, "Denied Access: Card Expired"),
|
||||||
|
(2042, "Denied Access: PIN Lockout"),
|
||||||
|
(2043, "Denied Access: Unassigned Card"),
|
||||||
|
(2044, "Denied Access: Unassigned Access PIN"),
|
||||||
|
(2046, "Denied Access: PIN Expired"),
|
||||||
|
(4034, "Alarm Acknowledged"),
|
||||||
|
(4035, "Door Locked: Scheduled"),
|
||||||
|
(4036, "Door Unlocked: Scheduled"),
|
||||||
|
(4041, "Door Forced Alarm"),
|
||||||
|
(4042, "Door Held Alarm"),
|
||||||
|
(4043, "Tamper Switch Alarm"),
|
||||||
|
(4044, "AC Failure"),
|
||||||
|
(4045, "Battery Failure"),
|
||||||
|
(4051, "REX Switch Alarm"),
|
||||||
|
(7020, "Time Set To"),
|
||||||
|
(12031, "Granted Access: Manual"),
|
||||||
|
(12032, "Door Unlocked"),
|
||||||
|
(12033, "Door Locked"),
|
||||||
|
],
|
||||||
|
db_column="eventType",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("reader_address", models.IntegerField(db_column="readerAddress")),
|
||||||
|
(
|
||||||
|
"cardholder_id",
|
||||||
|
models.IntegerField(
|
||||||
|
blank=True, db_column="cardholderID", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"command_status",
|
||||||
|
models.BooleanField(
|
||||||
|
blank=True, db_column="commandStatus", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("forename", models.TextField(blank=True, null=True)),
|
||||||
|
("surname", models.TextField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"io_state",
|
||||||
|
models.BooleanField(blank=True, db_column="ioState", null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"new_time",
|
||||||
|
models.DateTimeField(blank=True, db_column="newTime", null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"old_time",
|
||||||
|
models.DateTimeField(blank=True, db_column="oldTime", null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"raw_card_number",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, db_column="rawCardNumber", max_length=8, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "hidevent",
|
||||||
|
"ordering": ("-timestamp",),
|
||||||
|
"managed": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
doorcontrol/migrations/__init__.py
Normal file
0
doorcontrol/migrations/__init__.py
Normal file
122
doorcontrol/models.py
Normal file
122
doorcontrol/models.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class HIDEvent(models.Model):
|
||||||
|
class EventType(models.IntegerChoices):
|
||||||
|
DENIED_ACCESS_CARD_NOT_FOUND = 1022, "Denied Access: Card Not Found"
|
||||||
|
DENIED_ACCESS_ACCESS_PIN_NOT_FOUND = 1023, "Denied Access Access: PIN Not Found"
|
||||||
|
GRANTED_ACCESS = 2020, "Granted Access"
|
||||||
|
GRANTED_ACCESS_EXTENDED_TIME = 2021, "Granted Access: Extended Time"
|
||||||
|
DENIED_ACCESS_SCHEDULE = 2024, "Denied Access: Schedule"
|
||||||
|
DENIED_ACCESS_WRONG_PIN = 2029, "Denied Access: Wrong PIN"
|
||||||
|
DENIED_ACCESS_CARD_EXPIRED = 2036, "Denied Access: Card Expired"
|
||||||
|
DENIED_ACCESS_PIN_LOCKOUT = 2042, "Denied Access: PIN Lockout"
|
||||||
|
DENIED_ACCESS_UNASSIGNED_CARD = 2043, "Denied Access: Unassigned Card"
|
||||||
|
DENIED_ACCESS_UNASSIGNED_ACCESS_PIN = (
|
||||||
|
2044,
|
||||||
|
"Denied Access: Unassigned Access PIN",
|
||||||
|
)
|
||||||
|
DENIED_ACCESS_PIN_EXPIRED = 2046, "Denied Access: PIN Expired"
|
||||||
|
ALARM_ACKNOWLEDGED = 4034, "Alarm Acknowledged"
|
||||||
|
DOOR_LOCKED_SCHEDULED = 4035, "Door Locked: Scheduled"
|
||||||
|
DOOR_UNLOCKED_SCHEDULED = 4036, "Door Unlocked: Scheduled"
|
||||||
|
DOOR_FORCED_ALARM = 4041, "Door Forced Alarm"
|
||||||
|
DOOR_HELD_ALARM = 4042, "Door Held Alarm"
|
||||||
|
TAMPER_SWITCH_ALARM = 4043, "Tamper Switch Alarm"
|
||||||
|
AC_FAILURE = 4044, "AC Failure"
|
||||||
|
BATTERY_FAILURE = 4045, "Battery Failure"
|
||||||
|
REX_SWITCH_ALARM = 4051, "REX Switch Alarm"
|
||||||
|
TIME_SET_TO = 7020, "Time Set To"
|
||||||
|
GRANTED_ACCESS_MANUAL = 12031, "Granted Access: Manual"
|
||||||
|
DOOR_UNLOCKED = 12032, "Door Unlocked"
|
||||||
|
DOOR_LOCKED = 12033, "Door Locked"
|
||||||
|
|
||||||
|
door_name = models.CharField(max_length=64, db_column="doorName")
|
||||||
|
timestamp = models.DateTimeField()
|
||||||
|
event_type = models.IntegerField(db_column="eventType", choices=EventType.choices)
|
||||||
|
reader_address = models.IntegerField(db_column="readerAddress")
|
||||||
|
cardholder_id = models.IntegerField(blank=True, null=True, db_column="cardholderID")
|
||||||
|
command_status = models.BooleanField(
|
||||||
|
blank=True, null=True, db_column="commandStatus"
|
||||||
|
)
|
||||||
|
forename = models.TextField(blank=True, null=True)
|
||||||
|
surname = models.TextField(blank=True, null=True)
|
||||||
|
io_state = models.BooleanField(blank=True, null=True, db_column="ioState")
|
||||||
|
new_time = models.DateTimeField(blank=True, null=True, db_column="newTime")
|
||||||
|
old_time = models.DateTimeField(blank=True, null=True, db_column="oldTime")
|
||||||
|
raw_card_number = models.CharField(
|
||||||
|
max_length=8, blank=True, null=True, db_column="rawCardNumber"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
"""
|
||||||
|
Based on `Global.localeStrings` from /html/en_EN/en_EN.js
|
||||||
|
and `function eventDataHandler` from /html/modules/hid-dashboard.js
|
||||||
|
on a HID EDGE EVO Solo
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = f"{self.forename} {self.surname}"
|
||||||
|
direction = "IN" if self.reader_address == 0 else "OUT"
|
||||||
|
|
||||||
|
event_types = {
|
||||||
|
1022: f"Denied Access, {direction} Card Not Found {self.raw_card_number}",
|
||||||
|
1023: f"Denied Access, {direction} Access PIN Not Found {self.raw_card_number}",
|
||||||
|
2020: f"Granted Access, {direction} {name}",
|
||||||
|
2021: f"Granted Access, {direction} Extended Time {name}",
|
||||||
|
2024: f"Denied Access, {direction} Schedule {name}",
|
||||||
|
2029: f"Denied Access, {direction} Wrong PIN {name}",
|
||||||
|
2036: f"Denied Access, {direction} Card Expired {name}",
|
||||||
|
2042: f"Denied Access, {direction} PIN Lockout {name}",
|
||||||
|
2043: f"Denied Access, {direction} Unassigned Card {self.raw_card_number}",
|
||||||
|
2044: f"Denied Access, {direction} Unassigned Access PIN {self.raw_card_number}",
|
||||||
|
2046: f"Denied Access - PIN Expired {name}",
|
||||||
|
4034: "Alarm Acknowledged",
|
||||||
|
4035: "Door Locked-Scheduled",
|
||||||
|
4036: "Door Unlocked-Scheduled",
|
||||||
|
4041: "Door Forced Alarm",
|
||||||
|
4042: "Door Held Alarm",
|
||||||
|
4043: "Tamper Switch Alarm",
|
||||||
|
4044: "AC Failure",
|
||||||
|
4045: "Battery Failure",
|
||||||
|
4051: "REX Switch Alarm",
|
||||||
|
7020: f"Time Set to: {self.new_time}",
|
||||||
|
12031: f"Granted Access, {direction} Manual",
|
||||||
|
12032: "Door Unlocked",
|
||||||
|
12033: "Door Locked",
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_types.get(self.event_type, f"Unknown Event Type {self.event_type}")
|
||||||
|
|
||||||
|
@admin.display(boolean=True)
|
||||||
|
def is_red(self):
|
||||||
|
"""Based on `function isRedEvent` from /html/hid-global.js on a HID EDGE EVO Solo"""
|
||||||
|
return self.event_type in [
|
||||||
|
1022,
|
||||||
|
1023,
|
||||||
|
2024,
|
||||||
|
2029,
|
||||||
|
2036,
|
||||||
|
2042,
|
||||||
|
2043,
|
||||||
|
2046,
|
||||||
|
4041,
|
||||||
|
4042,
|
||||||
|
4043,
|
||||||
|
4044,
|
||||||
|
4045,
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.door_name} {self.timestamp} - {self.description}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["door_name", "timestamp", "event_type"], name="unique_hidevent"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
managed = False
|
||||||
|
db_table = "hidevent"
|
||||||
|
ordering = ("-timestamp",)
|
26
doorcontrol/routers.py
Normal file
26
doorcontrol/routers.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
class DoorControlRouter:
|
||||||
|
app_label = "doorcontrol"
|
||||||
|
db = "doors"
|
||||||
|
|
||||||
|
def db_for_read(self, model, **hints):
|
||||||
|
if model._meta.app_label == self.app_label:
|
||||||
|
return self.db
|
||||||
|
return None
|
||||||
|
|
||||||
|
def db_for_write(self, model, **hints):
|
||||||
|
if model._meta.app_label == self.app_label:
|
||||||
|
return self.db
|
||||||
|
return None
|
||||||
|
|
||||||
|
def allow_migrate(self, db, app_label, model_name=None, **hints):
|
||||||
|
if db == self.db:
|
||||||
|
return False
|
||||||
|
return None
|
||||||
|
|
||||||
|
def allow_relation(self, obj1, obj2, **hints):
|
||||||
|
if (
|
||||||
|
obj1._meta.app_label == self.app_label
|
||||||
|
or obj2._meta.app_label == self.app_label
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
return None
|
3
doorcontrol/tests.py
Normal file
3
doorcontrol/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
doorcontrol/views.py
Normal file
3
doorcontrol/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
Loading…
Reference in New Issue
Block a user