Compare commits
13 Commits
1aa0bd6014
...
eb42e2515b
Author | SHA1 | Date | |
---|---|---|---|
eb42e2515b | |||
ee001b0256 | |||
cb8bf3da4f | |||
05037b74fc | |||
1d9102d372 | |||
33024d7d62 | |||
2dfa0db316 | |||
d835645221 | |||
90808251d5 | |||
feed128a3b | |||
08ff37de7d | |||
28abbf69f8 | |||
4502c1b3ef |
@ -8,11 +8,11 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 23.7.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/Riverside-Healthcare/djLint
|
||||
rev: v1.19.16
|
||||
rev: v1.32.1
|
||||
hooks:
|
||||
- id: djlint-django
|
||||
|
@ -35,6 +35,7 @@ INSTALLED_APPS = [
|
||||
"recurrence",
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
"django_q",
|
||||
"tasks.apps.TasksConfig",
|
||||
"rentals.apps.RentalsConfig",
|
||||
"membershipworks.apps.MembershipworksConfig",
|
||||
@ -114,3 +115,14 @@ REST_FRAMEWORK = {
|
||||
],
|
||||
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",
|
||||
}
|
||||
|
||||
# Django Q
|
||||
Q_CLUSTER = {
|
||||
"name": "cmsmanage",
|
||||
"orm": "default",
|
||||
"retry": 360,
|
||||
"timeout": 300,
|
||||
"ALT_CLUSTERS": {
|
||||
"internal": {},
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import HIDEvent
|
||||
from .models import Door, HIDEvent
|
||||
|
||||
|
||||
@admin.register(Door)
|
||||
class DoorAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class IsRedFilter(admin.SimpleListFilter):
|
||||
@ -23,10 +28,10 @@ class IsRedFilter(admin.SimpleListFilter):
|
||||
@admin.register(HIDEvent)
|
||||
class HIDEventAdmin(admin.ModelAdmin):
|
||||
search_fields = ["forename", "surname", "cardholder_id"]
|
||||
list_display = ["door_name", "timestamp", "event_type", "description", "is_red"]
|
||||
list_display = ["timestamp", "door", "event_type", "description", "is_red"]
|
||||
list_filter = [
|
||||
"timestamp",
|
||||
"door_name",
|
||||
"door",
|
||||
"event_type",
|
||||
IsRedFilter,
|
||||
]
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
@ -96,4 +95,10 @@ class Migration(migrations.Migration):
|
||||
"managed": False,
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="hidevent",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("door_name", "timestamp", "event_type"), name="unique_hidevent"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@ -0,0 +1,74 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-19 04:20
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def link_events_to_doors(apps, schema_editor):
|
||||
HIDEvent = apps.get_model("doorcontrol", "HIDEvent")
|
||||
Door = apps.get_model("doorcontrol", "Door")
|
||||
for event in HIDEvent.objects.all():
|
||||
door, created = Door.objects.get_or_create(name=event.door_name)
|
||||
event.door = door
|
||||
event.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("doorcontrol", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="hidevent",
|
||||
options={"ordering": ("-timestamp",)},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Door",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=64, unique=True)),
|
||||
],
|
||||
),
|
||||
# create nullable foreign key to door
|
||||
migrations.AddField(
|
||||
model_name="hidevent",
|
||||
name="door",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.door",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
# create new Doors and link them to HID Events
|
||||
migrations.RunPython(link_events_to_doors, atomic=True),
|
||||
# make door foreign key not nullable
|
||||
migrations.AlterField(
|
||||
model_name="hidevent",
|
||||
name="door",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="doorcontrol.door"
|
||||
),
|
||||
),
|
||||
# remove old constaint
|
||||
migrations.RemoveConstraint(model_name="hidevent", name="unique_hidevent"),
|
||||
# remove old name field
|
||||
migrations.RemoveField(
|
||||
model_name="hidevent",
|
||||
name="door_name",
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="hidevent",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("door", "timestamp", "event_type"), name="unique_hidevent"
|
||||
),
|
||||
),
|
||||
]
|
@ -3,6 +3,13 @@ from django.db.models import ExpressionWrapper, F, Func, Q
|
||||
from django.db.models.functions import Mod
|
||||
|
||||
|
||||
class Door(models.Model):
|
||||
name = models.CharField(max_length=64, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class HIDEventQuerySet(models.QuerySet):
|
||||
def with_is_red(self):
|
||||
"""Based on `function isRedEvent` from /html/hid-global.js on a HID EDGE EVO Solo"""
|
||||
@ -10,19 +17,19 @@ class HIDEventQuerySet(models.QuerySet):
|
||||
is_red=ExpressionWrapper(
|
||||
Q(
|
||||
event_type__in=[
|
||||
1022,
|
||||
1023,
|
||||
2024,
|
||||
2029,
|
||||
2036,
|
||||
2042,
|
||||
2043,
|
||||
2046,
|
||||
4041,
|
||||
4042,
|
||||
4043,
|
||||
4044,
|
||||
4045,
|
||||
HIDEvent.EventType.DENIED_ACCESS_CARD_NOT_FOUND,
|
||||
HIDEvent.EventType.DENIED_ACCESS_ACCESS_PIN_NOT_FOUND,
|
||||
HIDEvent.EventType.DENIED_ACCESS_SCHEDULE,
|
||||
HIDEvent.EventType.DENIED_ACCESS_WRONG_PIN,
|
||||
HIDEvent.EventType.DENIED_ACCESS_CARD_EXPIRED,
|
||||
HIDEvent.EventType.DENIED_ACCESS_PIN_LOCKOUT,
|
||||
HIDEvent.EventType.DENIED_ACCESS_UNASSIGNED_CARD,
|
||||
HIDEvent.EventType.DENIED_ACCESS_PIN_EXPIRED,
|
||||
HIDEvent.EventType.DOOR_FORCED_ALARM,
|
||||
HIDEvent.EventType.DOOR_HELD_ALARM,
|
||||
HIDEvent.EventType.TAMPER_SWITCH_ALARM,
|
||||
HIDEvent.EventType.AC_FAILURE,
|
||||
HIDEvent.EventType.BATTERY_FAILURE,
|
||||
]
|
||||
),
|
||||
output_field=models.BooleanField(),
|
||||
@ -98,7 +105,7 @@ class HIDEvent(models.Model):
|
||||
DOOR_UNLOCKED = 12032, "Door Unlocked"
|
||||
DOOR_LOCKED = 12033, "Door Locked"
|
||||
|
||||
door_name = models.CharField(max_length=64, db_column="doorName")
|
||||
door = models.ForeignKey(Door, on_delete=models.CASCADE)
|
||||
timestamp = models.DateTimeField()
|
||||
event_type = models.IntegerField(db_column="eventType", choices=EventType.choices)
|
||||
reader_address = models.IntegerField(db_column="readerAddress")
|
||||
@ -156,7 +163,7 @@ class HIDEvent(models.Model):
|
||||
return event_types.get(self.event_type, f"Unknown Event Type {self.event_type}")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.door_name} {self.timestamp} - {self.description}"
|
||||
return f"{self.door.name} {self.timestamp} - {self.description}"
|
||||
|
||||
def decoded_card_number(self) -> str:
|
||||
"""Requires annotations from `with_decoded_card_number`"""
|
||||
@ -173,9 +180,8 @@ class HIDEvent(models.Model):
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["door_name", "timestamp", "event_type"], name="unique_hidevent"
|
||||
fields=["door", "timestamp", "event_type"], name="unique_hidevent"
|
||||
)
|
||||
]
|
||||
managed = False
|
||||
db_table = "hidevent"
|
||||
ordering = ("-timestamp",)
|
||||
|
@ -13,8 +13,8 @@ class DoorControlRouter:
|
||||
return None
|
||||
|
||||
def allow_migrate(self, db, app_label, model_name=None, **hints):
|
||||
if db == self.db:
|
||||
return False
|
||||
if app_label == self.app_label:
|
||||
return db == self.db
|
||||
return None
|
||||
|
||||
def allow_relation(self, obj1, obj2, **hints):
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
{% block title %}{{ selected_report }} | Door Controls | CMS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<ul class="nav nav-tabs">
|
||||
{% for report_name, report_url in report_types %}
|
||||
<li class="nav-item">
|
||||
@ -10,10 +11,8 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form method="get" class="form-floating">
|
||||
<div class="row align-items-center row-cols-md-auto g-2 mb-2 mt-2">
|
||||
<div class="col-12">
|
||||
<div class="form-floating">
|
||||
<form method="get" class="form-floating m-2 d-flex align-items-center">
|
||||
<div class="form-floating mx-2">
|
||||
<input type="date"
|
||||
class="form-control"
|
||||
id="startDate"
|
||||
@ -21,9 +20,7 @@
|
||||
value="{{ timestamp__gte|date:'Y-m-d' }}">
|
||||
<label for="startDate">Start Date</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-floating">
|
||||
<div class="form-floating mx-2">
|
||||
<input type="date"
|
||||
class="form-control"
|
||||
id="endDate"
|
||||
@ -31,9 +28,7 @@
|
||||
value="{{ timestamp__lte|date:'Y-m-d' }}">
|
||||
<label for="endDate">End Date</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-2">
|
||||
<div class="form-floating">
|
||||
<div class="form-floating mx-1">
|
||||
<input type="number"
|
||||
class="form-control"
|
||||
id="itemsPerPage"
|
||||
@ -45,14 +40,10 @@
|
||||
required>
|
||||
<label for="itemsPerPage">Items Per Page</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
<a href="?" class="btn btn-warning">Reset</a>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mx-2">Submit</button>
|
||||
<a href="?" class="btn btn-warning mx-1">Reset</a>
|
||||
</form>
|
||||
<table class="table table-bordered table-striped table-hover mb-2">
|
||||
<table class="table table-striped table-hover mb-2 w-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for column in object_list.0.keys %}<th>{{ column|title }}</th>{% endfor %}
|
||||
@ -88,4 +79,5 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -163,7 +163,7 @@ class DeniedAccess(BaseAccessReport):
|
||||
return [
|
||||
{
|
||||
"timestamp": event.timestamp,
|
||||
"door name": event.door_name,
|
||||
"door name": event.door.name,
|
||||
"event type": HIDEvent.EventType(event.event_type).label,
|
||||
"name": " ".join(
|
||||
(n for n in [event.forename, event.surname] if n is not None)
|
||||
|
@ -5,7 +5,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("membershipworks", "0001_initial"),
|
||||
]
|
||||
|
@ -24,11 +24,9 @@ class MemberQuerySet(models.QuerySet):
|
||||
# TODO: maybe rename to reflect EXISTS?
|
||||
@staticmethod
|
||||
def has_flag(flag_type: str, flag_name: str):
|
||||
return Exists(Flag.objects.filter(
|
||||
type=flag_type,
|
||||
name=flag_name,
|
||||
members=OuterRef("pk")
|
||||
))
|
||||
return Exists(
|
||||
Flag.objects.filter(type=flag_type, name=flag_name, members=OuterRef("pk"))
|
||||
)
|
||||
|
||||
# TODO: it should be fairly easy to reduce the number of EXISTS by
|
||||
# merging the ORed flags
|
||||
|
@ -54,6 +54,7 @@ def department_emails(ordered_queryset):
|
||||
)
|
||||
|
||||
for department, certifications in certifications_by_department:
|
||||
if department.shop_lead_flag is not None:
|
||||
yield make_department_email(department, list(certifications))
|
||||
|
||||
|
||||
|
@ -5,7 +5,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
@ -19,7 +19,6 @@ def migrate_certification_version_forward(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("paperwork", "0001_initial"),
|
||||
]
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("paperwork", "0002_add_certification_version_model"),
|
||||
]
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("paperwork", "0003_alter_certificationversion_id"),
|
||||
]
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("paperwork", "0004_alter_certification_options"),
|
||||
]
|
||||
|
@ -17,7 +17,6 @@ def link_departments(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("membershipworks", "0001_initial"),
|
||||
("paperwork", "0005_certificationdefinition_mailing_list"),
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("paperwork", "0006_department_alter_certificationdefinition_department"),
|
||||
]
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("paperwork", "0007_department_has_mailing_list"),
|
||||
]
|
||||
|
@ -5,7 +5,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("membershipworks", "0002_alter_flag_options"),
|
||||
("paperwork", "0008_remove_certificationdefinition_mailing_list"),
|
||||
|
@ -1,5 +1,5 @@
|
||||
[project]
|
||||
name = "CMS Management"
|
||||
name = "CMSManage"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [
|
||||
@ -8,30 +8,34 @@ authors = [
|
||||
dependencies = [
|
||||
"django~=4.2",
|
||||
"django-admin-logs~=1.0",
|
||||
"django-auth-ldap~=4.3",
|
||||
"django-auth-ldap~=4.5",
|
||||
"django-markdownx~=4.0",
|
||||
"django-recurrence~=1.11",
|
||||
"django-widget-tweaks~=1.4",
|
||||
"django-widget-tweaks~=1.5",
|
||||
"django-stubs-ext~=4.2",
|
||||
"markdownify~=0.11",
|
||||
"mdformat~=0.7",
|
||||
"mdformat-tables~=0.4",
|
||||
"mysqlclient~=2.1",
|
||||
"mysqlclient~=2.2",
|
||||
"bleach~=6.0",
|
||||
"django-autocomplete-light~=3.9",
|
||||
"weasyprint~=59.0",
|
||||
"requests~=2.31",
|
||||
"semver~=3.0",
|
||||
"djangorestframework~=3.14",
|
||||
"django-q2~=1.5",
|
||||
]
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[project.optional-dependencies]
|
||||
server = [
|
||||
"uvicorn~=0.22",
|
||||
"setuptools~=68.0",
|
||||
"uvicorn~=0.23",
|
||||
"setuptools~=68.2",
|
||||
]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
|
||||
[tool.djlint]
|
||||
profile="django"
|
||||
extension = ".dj.html"
|
||||
@ -64,20 +68,20 @@ name = "pypi"
|
||||
|
||||
[tool.pdm.dev-dependencies]
|
||||
lint = [
|
||||
"black~=23.3",
|
||||
"djlint~=1.31",
|
||||
"black~=23.7",
|
||||
"djlint~=1.32",
|
||||
]
|
||||
typing = [
|
||||
"mypy~=1.3",
|
||||
"mypy~=1.4",
|
||||
"django-stubs~=4.2",
|
||||
"setuptools~=68.0",
|
||||
"setuptools~=68.2",
|
||||
"types-bleach~=6.0",
|
||||
"types-requests~=2.31",
|
||||
"types-urllib3~=1.26",
|
||||
"djangorestframework-stubs[compatible-mypy]~=3.14",
|
||||
]
|
||||
debug = [
|
||||
"django-debug-toolbar~=4.1",
|
||||
"django-debug-toolbar~=4.2",
|
||||
]
|
||||
|
||||
[tool.pdm.scripts]
|
||||
@ -85,5 +89,5 @@ start = "./manage.py runserver"
|
||||
fmt.shell = "black . && djlint --reformat ."
|
||||
|
||||
[build-system]
|
||||
requires = ["pdm-pep517"]
|
||||
build-backend = "pdm.pep517.api"
|
||||
requires = ["pdm-backend"]
|
||||
build-backend = "pdm.backend"
|
||||
|
@ -6,7 +6,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("rentals", "0001_initial"),
|
||||
]
|
||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("rentals", "0002_lockerinfo_notes"),
|
||||
]
|
||||
|
95
static/bootstrap-color-toggle.js
vendored
Normal file
95
static/bootstrap-color-toggle.js
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/*!
|
||||
* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
|
||||
* Copyright 2011-2023 The Bootstrap Authors
|
||||
* Modified by Adam Goldsmith
|
||||
* Licensed under the Creative Commons Attribution 3.0 Unported License.
|
||||
*/
|
||||
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
const getStoredTheme = () => localStorage.getItem("theme");
|
||||
const setStoredTheme = (theme) => localStorage.setItem("theme", theme);
|
||||
|
||||
const getPreferredTheme = () => {
|
||||
const storedTheme = getStoredTheme();
|
||||
if (storedTheme) {
|
||||
return storedTheme;
|
||||
}
|
||||
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
};
|
||||
|
||||
const setTheme = (theme) => {
|
||||
if (
|
||||
theme === "auto" &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
document.documentElement.setAttribute("data-bs-theme", "dark");
|
||||
} else {
|
||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||
}
|
||||
};
|
||||
|
||||
setTheme(getPreferredTheme());
|
||||
|
||||
const showActiveTheme = (theme, focus = false) => {
|
||||
const themeSwitcher = document.querySelector("#bd-theme");
|
||||
|
||||
if (!themeSwitcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
const themeSwitcherText = document.querySelector("#bd-theme-text");
|
||||
const activeThemeIcon = document.querySelector(".theme-icon-active");
|
||||
const btnToActive = document.querySelector(
|
||||
`[data-bs-theme-value="${theme}"]`,
|
||||
);
|
||||
const activeIcon = [
|
||||
...btnToActive.querySelector(".theme-icon").classList,
|
||||
].find((c) => c.startsWith("bi-"));
|
||||
|
||||
document.querySelectorAll("[data-bs-theme-value]").forEach((element) => {
|
||||
element.classList.remove("active");
|
||||
element.setAttribute("aria-pressed", "false");
|
||||
});
|
||||
|
||||
btnToActive.classList.add("active");
|
||||
btnToActive.setAttribute("aria-pressed", "true");
|
||||
[...activeThemeIcon.classList]
|
||||
.filter((c) => c.startsWith("bi-"))
|
||||
.forEach((icon) => activeThemeIcon.classList.remove(icon));
|
||||
activeThemeIcon.classList.add(activeIcon);
|
||||
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`;
|
||||
themeSwitcher.setAttribute("aria-label", themeSwitcherLabel);
|
||||
|
||||
if (focus) {
|
||||
themeSwitcher.focus();
|
||||
}
|
||||
};
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", () => {
|
||||
const storedTheme = getStoredTheme();
|
||||
if (storedTheme !== "light" && storedTheme !== "dark") {
|
||||
setTheme(getPreferredTheme());
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
console.log(getPreferredTheme());
|
||||
showActiveTheme(getPreferredTheme());
|
||||
|
||||
document.querySelectorAll("[data-bs-theme-value]").forEach((toggle) => {
|
||||
toggle.addEventListener("click", () => {
|
||||
const theme = toggle.getAttribute("data-bs-theme-value");
|
||||
setStoredTheme(theme);
|
||||
setTheme(theme);
|
||||
showActiveTheme(theme, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
5
static/bootstrap-icons.min.css
vendored
Normal file
5
static/bootstrap-icons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
static/bootstrap.bundle.min.js
vendored
6
static/bootstrap.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
7
static/bootstrap.min.css
vendored
7
static/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
BIN
static/fonts/bootstrap-icons.woff
Normal file
BIN
static/fonts/bootstrap-icons.woff
Normal file
Binary file not shown.
BIN
static/fonts/bootstrap-icons.woff2
Normal file
BIN
static/fonts/bootstrap-icons.woff2
Normal file
Binary file not shown.
4
static/tabulator.min.js
vendored
4
static/tabulator.min.js
vendored
File diff suppressed because one or more lines are too long
2
static/tabulator_bootstrap5.min.css
vendored
2
static/tabulator_bootstrap5.min.css
vendored
File diff suppressed because one or more lines are too long
@ -7,7 +7,6 @@ import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
@ -10,7 +10,6 @@ def slugify_name(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("tasks", "0001_initial"),
|
||||
]
|
||||
|
@ -8,6 +8,8 @@
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet">
|
||||
<!-- Bootstrap Icons CSS -->
|
||||
<link href="{% static 'bootstrap-icons.min.css' %}" rel="stylesheet">
|
||||
<!-- Tabulator CSS -->
|
||||
<link href="{% static 'tabulator_bootstrap5.min.css' %}" rel="stylesheet">
|
||||
<title>
|
||||
@ -15,7 +17,8 @@
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-sm navbar-light bg-light">
|
||||
<script src="{% static 'bootstrap-color-toggle.js' %}"></script>
|
||||
<nav class="navbar navbar-expand-sm bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{% url 'dashboard:dashboard' %}">Claremont MakerSpace</a>
|
||||
<button class="navbar-toggler"
|
||||
@ -30,6 +33,35 @@
|
||||
<div id="user-nav" class="collapse navbar-collapse justify-content-end">
|
||||
<div class="navbar-nav">
|
||||
{% block nav_extra %}{% endblock %}
|
||||
<div class="nav-item dropdown">
|
||||
<button class="btn btn-link nav-link dropdown-toggle d-flex align-items-center" id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static" aria-label="Toggle theme (auto)">
|
||||
<i class="bi theme-icon-active bi-circle-half"></i>
|
||||
<span class="d-none ms-2" id="bd-theme-text">Toggle theme</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
|
||||
<i class="bi me-2 opacity-50 theme-icon bi-sun-fill"></i>
|
||||
Light
|
||||
<i class="bi ms-auto d-none bi-check2"></i>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
|
||||
<i class="bi me-2 opacity-50 theme-icon bi-moon-stars-fill"></i>
|
||||
Dark
|
||||
<i class="bi ms-auto d-none bi-check2"></i>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
|
||||
<i class="bi me-2 opacity-50 theme-icon bi-circle-half"></i>
|
||||
Auto
|
||||
<i class="bi ms-auto d-none bi-check2"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% if user.is_staff %}
|
||||
<a class="nav-item nav-link"
|
||||
href="{% block admin_link %}{% url 'admin:index' %}{% endblock %}">Admin</a>
|
||||
@ -66,10 +98,10 @@
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% block footer %}{% endblock %}
|
||||
</body>
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="{% static 'bootstrap.bundle.min.js' %}"></script>
|
||||
<!-- Tabulator JS -->
|
||||
<script src="{% static 'tabulator.min.js' %}"></script>
|
||||
{% block script %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user