2024-01-18 13:19:58 -05:00
|
|
|
from typing import Any, cast
|
2023-04-10 14:29:12 -04:00
|
|
|
|
2023-04-10 13:12:45 -04:00
|
|
|
from django import forms
|
2022-02-14 11:05:35 -05:00
|
|
|
from django.contrib import admin, messages
|
2024-01-17 21:17:24 -05:00
|
|
|
from django.core import mail
|
2023-02-02 22:33:13 -05:00
|
|
|
from django.db.models import Value
|
2024-01-17 21:17:24 -05:00
|
|
|
from django.db.models.functions import Concat, LPad, Now
|
2023-04-10 14:29:12 -04:00
|
|
|
from django.db.models.query import QuerySet
|
|
|
|
from django.http import HttpRequest
|
2021-03-23 17:08:00 -04:00
|
|
|
|
2024-01-17 21:17:24 -05:00
|
|
|
from .certification_emails import all_certification_emails
|
|
|
|
from .forms import CertificationForm
|
2022-02-11 13:48:47 -05:00
|
|
|
from .models import (
|
2023-04-10 14:29:12 -04:00
|
|
|
AbstractAudit,
|
2022-02-11 13:48:47 -05:00
|
|
|
Certification,
|
2023-04-10 13:12:45 -04:00
|
|
|
CertificationAudit,
|
2024-01-17 21:17:24 -05:00
|
|
|
CertificationDefinition,
|
2022-02-11 13:48:47 -05:00
|
|
|
CertificationVersion,
|
2023-04-10 14:29:12 -04:00
|
|
|
CertificationVersionAnnotated,
|
2024-01-17 21:17:24 -05:00
|
|
|
CmsRedRiverVeteransScholarship,
|
|
|
|
Department,
|
2022-02-11 13:48:47 -05:00
|
|
|
InstructorOrVendor,
|
|
|
|
SpecialProgram,
|
|
|
|
Waiver,
|
2023-04-10 13:12:45 -04:00
|
|
|
WaiverAudit,
|
2022-02-11 13:48:47 -05:00
|
|
|
)
|
2021-03-23 17:08:00 -04:00
|
|
|
|
|
|
|
|
2023-04-10 13:12:45 -04:00
|
|
|
class AlwaysChangedModelForm(forms.models.ModelForm):
|
|
|
|
"""By always returning true even unchanged inlines will get validated and saved."""
|
|
|
|
|
2023-04-10 14:29:12 -04:00
|
|
|
def has_changed(self) -> bool:
|
2023-04-10 13:12:45 -04:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class AbstractAuditInline(admin.TabularInline):
|
|
|
|
extra = 0
|
|
|
|
form = AlwaysChangedModelForm
|
|
|
|
|
2023-04-10 14:29:12 -04:00
|
|
|
def get_formset(
|
2024-01-18 13:19:58 -05:00
|
|
|
self, request: HttpRequest, obj: AbstractAudit | None = None, **kwargs: Any
|
|
|
|
) -> type[
|
2023-04-10 14:29:12 -04:00
|
|
|
"forms.models.BaseInlineFormSet[AbstractAudit, Any, forms.models.ModelForm[Any]]"
|
|
|
|
]:
|
2023-04-10 13:12:45 -04:00
|
|
|
formset = super().get_formset(request, obj, **kwargs)
|
|
|
|
formset.form.base_fields["user"].initial = request.user
|
|
|
|
return formset
|
|
|
|
|
|
|
|
|
2023-01-18 21:29:36 -05:00
|
|
|
@admin.register(Department)
|
2023-02-02 22:33:13 -05:00
|
|
|
class DepartmentAdmin(admin.ModelAdmin):
|
2023-01-18 21:29:36 -05:00
|
|
|
search_fields = ["name"]
|
2023-01-19 16:02:17 -05:00
|
|
|
list_display = [
|
|
|
|
"name",
|
|
|
|
"parent",
|
|
|
|
"has_mailing_list",
|
2023-01-23 21:06:06 -05:00
|
|
|
"shop_lead_flag",
|
2023-01-19 16:02:17 -05:00
|
|
|
"list_reply_to_address",
|
|
|
|
]
|
2023-01-18 21:29:36 -05:00
|
|
|
|
|
|
|
|
2022-02-04 16:30:52 -05:00
|
|
|
class CertificationVersionInline(admin.TabularInline):
|
|
|
|
model = CertificationVersion
|
|
|
|
extra = 1
|
|
|
|
|
2022-11-07 13:49:39 -05:00
|
|
|
readonly_fields = (
|
|
|
|
"semantic_version",
|
|
|
|
"is_latest",
|
|
|
|
"is_current",
|
|
|
|
)
|
|
|
|
|
2023-02-01 00:11:28 -05:00
|
|
|
@admin.display(description="Latest", boolean=True)
|
2023-04-10 14:29:12 -04:00
|
|
|
def is_latest(self, obj: CertificationVersionAnnotated) -> bool:
|
2023-02-01 00:11:28 -05:00
|
|
|
return obj.is_latest
|
|
|
|
|
|
|
|
@admin.display(description="Current", boolean=True)
|
2023-04-10 14:29:12 -04:00
|
|
|
def is_current(self, obj: CertificationVersionAnnotated) -> bool:
|
2023-02-01 00:11:28 -05:00
|
|
|
return obj.is_current
|
|
|
|
|
2022-02-04 16:30:52 -05:00
|
|
|
|
2022-02-03 13:45:58 -05:00
|
|
|
@admin.register(CertificationDefinition)
|
2021-03-23 17:08:00 -04:00
|
|
|
class CertificationDefinitionAdmin(admin.ModelAdmin):
|
2023-04-10 14:51:43 -04:00
|
|
|
search_fields = ["name"]
|
2022-11-07 13:49:39 -05:00
|
|
|
list_display = [
|
2023-04-10 14:51:43 -04:00
|
|
|
"name",
|
2022-11-07 13:49:39 -05:00
|
|
|
"department",
|
2023-01-31 18:53:36 -05:00
|
|
|
"latest_semantic_version",
|
2022-11-07 13:49:39 -05:00
|
|
|
]
|
2022-02-11 13:48:47 -05:00
|
|
|
list_filter = ["department"]
|
2023-12-04 13:08:36 -05:00
|
|
|
show_facets = admin.ShowFacets.ALWAYS
|
2022-02-04 16:30:52 -05:00
|
|
|
inlines = [CertificationVersionInline]
|
2021-03-23 17:08:00 -04:00
|
|
|
|
2022-11-07 13:49:39 -05:00
|
|
|
@admin.display(description="Latest Version")
|
2023-04-10 14:29:12 -04:00
|
|
|
def latest_semantic_version(self, obj: CertificationDefinition) -> str:
|
|
|
|
return str(obj.latest_version().semantic_version())
|
2022-11-07 13:49:39 -05:00
|
|
|
|
2021-03-23 17:08:00 -04:00
|
|
|
|
2023-04-10 13:12:45 -04:00
|
|
|
class CertificationAuditInline(AbstractAuditInline):
|
|
|
|
model = CertificationAudit
|
|
|
|
|
|
|
|
|
2022-02-03 13:45:58 -05:00
|
|
|
@admin.register(Certification)
|
2021-03-23 17:08:00 -04:00
|
|
|
class CertificationAdmin(admin.ModelAdmin):
|
2023-04-01 11:37:49 -04:00
|
|
|
form = CertificationForm
|
2022-02-11 13:48:47 -05:00
|
|
|
search_fields = [
|
|
|
|
"name",
|
2023-04-10 14:51:43 -04:00
|
|
|
"certification_version__definition__name",
|
2023-03-02 20:24:06 -05:00
|
|
|
"certification_version__definition__department__name",
|
2022-02-11 13:48:47 -05:00
|
|
|
]
|
2023-12-29 18:19:58 -05:00
|
|
|
date_hierarchy = "date"
|
2022-02-11 13:48:47 -05:00
|
|
|
autocomplete_fields = ["member"]
|
|
|
|
exclude = ["shop_lead_notified"]
|
2023-04-10 13:12:45 -04:00
|
|
|
inlines = [CertificationAuditInline]
|
2022-02-05 00:28:58 -05:00
|
|
|
|
2023-04-10 14:29:12 -04:00
|
|
|
def get_queryset(self, request: HttpRequest) -> QuerySet[Certification]:
|
2023-02-01 00:11:28 -05:00
|
|
|
qs = super().get_queryset(request)
|
|
|
|
return qs.prefetch_related("certification_version__definition__department")
|
|
|
|
|
2022-02-11 13:48:47 -05:00
|
|
|
@admin.display(
|
|
|
|
description="Certification Name",
|
2023-04-10 14:51:43 -04:00
|
|
|
ordering="certification_version__definition__name",
|
2022-02-11 13:48:47 -05:00
|
|
|
)
|
2023-04-10 15:04:29 -04:00
|
|
|
def certification_name(self, obj: Certification) -> str:
|
2023-04-10 14:51:43 -04:00
|
|
|
return obj.certification_version.definition.name
|
2022-02-05 00:28:58 -05:00
|
|
|
|
2022-02-11 13:48:47 -05:00
|
|
|
@admin.display(
|
2023-01-31 18:53:36 -05:00
|
|
|
description="Certification Version",
|
|
|
|
ordering=(
|
|
|
|
Concat(
|
2023-02-02 22:33:13 -05:00
|
|
|
LPad("certification_version__major", 4, Value("0")),
|
|
|
|
LPad("certification_version__minor", 4, Value("0")),
|
|
|
|
LPad("certification_version__patch", 4, Value("0")),
|
2023-01-31 18:53:36 -05:00
|
|
|
"certification_version__prerelease",
|
|
|
|
"certification_version__approval_date",
|
|
|
|
)
|
|
|
|
),
|
2022-02-11 13:48:47 -05:00
|
|
|
)
|
2023-04-10 14:29:12 -04:00
|
|
|
def certification_semantic_version(self, obj: Certification) -> str:
|
|
|
|
return str(obj.certification_version.semantic_version())
|
2022-02-05 00:28:58 -05:00
|
|
|
|
2022-11-07 13:49:39 -05:00
|
|
|
@admin.display(description="Current", boolean=True)
|
2023-04-10 14:29:12 -04:00
|
|
|
def is_current(self, obj: Certification) -> bool:
|
|
|
|
return cast(CertificationVersionAnnotated, obj.certification_version).is_current
|
2022-11-07 13:49:39 -05:00
|
|
|
|
2022-02-11 13:48:47 -05:00
|
|
|
@admin.display(
|
|
|
|
description="Department",
|
|
|
|
ordering="certification_version__definition__department",
|
|
|
|
)
|
2023-04-10 14:29:12 -04:00
|
|
|
def certification_department(self, obj: Certification) -> Department:
|
2022-02-05 00:28:58 -05:00
|
|
|
return obj.certification_version.definition.department
|
|
|
|
|
2023-04-10 13:12:45 -04:00
|
|
|
@admin.display(description="Latest Audit")
|
2023-04-10 14:29:12 -04:00
|
|
|
def latest_audit(self, obj: Certification) -> CertificationAudit:
|
2023-04-10 13:12:45 -04:00
|
|
|
return obj.audits.latest()
|
|
|
|
|
2022-02-05 00:28:58 -05:00
|
|
|
list_display = [
|
2023-04-10 14:51:43 -04:00
|
|
|
"name",
|
2023-04-10 15:04:29 -04:00
|
|
|
"certification_name",
|
2023-01-31 18:53:36 -05:00
|
|
|
"certification_semantic_version",
|
2023-02-01 00:11:28 -05:00
|
|
|
"is_current",
|
2022-02-11 13:48:47 -05:00
|
|
|
"certification_department",
|
|
|
|
"date",
|
|
|
|
"shop_lead_notified",
|
|
|
|
"certified_by",
|
2023-04-10 13:12:45 -04:00
|
|
|
"latest_audit",
|
2022-02-05 00:28:58 -05:00
|
|
|
]
|
|
|
|
list_display_links = [
|
2023-04-10 14:51:43 -04:00
|
|
|
"name",
|
2023-04-10 15:04:29 -04:00
|
|
|
"certification_name",
|
2022-02-05 00:28:58 -05:00
|
|
|
]
|
|
|
|
list_filter = [
|
2022-02-11 13:48:47 -05:00
|
|
|
"certification_version__definition__department",
|
|
|
|
("shop_lead_notified", admin.EmptyFieldListFilter),
|
2023-04-10 13:12:45 -04:00
|
|
|
("audits", admin.EmptyFieldListFilter),
|
2022-02-05 00:28:58 -05:00
|
|
|
]
|
2023-12-04 13:08:36 -05:00
|
|
|
show_facets = admin.ShowFacets.ALWAYS
|
2021-03-23 17:08:00 -04:00
|
|
|
|
2022-02-14 11:05:35 -05:00
|
|
|
actions = ["send_notifications"]
|
|
|
|
|
|
|
|
@admin.action(
|
|
|
|
description="Notify Shop Leads and Members of selected certifications"
|
|
|
|
)
|
2023-04-10 14:29:12 -04:00
|
|
|
def send_notifications(
|
|
|
|
self, request: HttpRequest, queryset: QuerySet[Certification]
|
|
|
|
) -> None:
|
2022-02-14 11:05:35 -05:00
|
|
|
try:
|
|
|
|
emails = list(all_certification_emails(queryset))
|
|
|
|
|
|
|
|
with mail.get_connection() as conn:
|
|
|
|
conn.send_messages(emails)
|
|
|
|
|
|
|
|
for cert in queryset:
|
2022-02-24 21:59:16 -05:00
|
|
|
cert.shop_lead_notified = Now()
|
|
|
|
cert.save()
|
2022-02-14 11:05:35 -05:00
|
|
|
|
|
|
|
self.message_user(
|
|
|
|
request,
|
|
|
|
f"{len(emails)} notifications sent for {len(queryset)} certifications",
|
|
|
|
messages.SUCCESS,
|
|
|
|
)
|
|
|
|
except Exception as e:
|
|
|
|
self.message_user(
|
|
|
|
request,
|
|
|
|
f"Failed to send notifications! {e}",
|
|
|
|
messages.ERROR,
|
|
|
|
)
|
|
|
|
raise
|
|
|
|
|
2021-03-23 17:08:00 -04:00
|
|
|
|
2022-02-03 13:45:58 -05:00
|
|
|
@admin.register(InstructorOrVendor)
|
2021-03-23 17:08:00 -04:00
|
|
|
class InstructorOrVendorAdmin(admin.ModelAdmin):
|
2022-02-11 13:48:47 -05:00
|
|
|
search_fields = ["name"]
|
2022-07-21 18:49:24 -04:00
|
|
|
list_display = [
|
|
|
|
"name",
|
|
|
|
"instructor_agreement_date",
|
|
|
|
"w9_date",
|
|
|
|
"phone",
|
|
|
|
"email_address",
|
|
|
|
]
|
2021-03-23 17:08:00 -04:00
|
|
|
|
|
|
|
|
2022-02-03 13:45:58 -05:00
|
|
|
@admin.register(SpecialProgram)
|
2021-03-23 17:08:00 -04:00
|
|
|
class SpecialProgramAdmin(admin.ModelAdmin):
|
2022-02-11 13:48:47 -05:00
|
|
|
search_fields = ["program_name"]
|
2022-07-21 18:49:24 -04:00
|
|
|
list_display = [
|
|
|
|
"program_name",
|
|
|
|
"discount_percent",
|
|
|
|
"discount_code",
|
|
|
|
"membership_code",
|
|
|
|
"start_date",
|
|
|
|
"end_date",
|
|
|
|
"program_amount",
|
|
|
|
"program_status",
|
|
|
|
]
|
2021-03-23 17:08:00 -04:00
|
|
|
|
|
|
|
|
2023-04-10 13:12:45 -04:00
|
|
|
class WaiverAuditInline(AbstractAuditInline):
|
|
|
|
model = WaiverAudit
|
|
|
|
|
|
|
|
|
2022-02-03 13:45:58 -05:00
|
|
|
@admin.register(Waiver)
|
2021-03-23 17:08:00 -04:00
|
|
|
class WaiverAdmin(admin.ModelAdmin):
|
2022-02-11 13:48:47 -05:00
|
|
|
search_fields = ["name"]
|
2022-07-21 18:49:24 -04:00
|
|
|
list_display = [
|
|
|
|
"name",
|
|
|
|
"date",
|
|
|
|
"emergency_contact_name",
|
|
|
|
"emergency_contact_number",
|
|
|
|
"waiver_version",
|
|
|
|
"guardian_name",
|
|
|
|
"guardian_relation",
|
|
|
|
"guardian_date",
|
2023-04-10 13:12:45 -04:00
|
|
|
"latest_audit",
|
2022-07-21 18:49:24 -04:00
|
|
|
]
|
2023-04-10 13:12:45 -04:00
|
|
|
list_filter = [
|
|
|
|
"waiver_version",
|
|
|
|
("audits", admin.EmptyFieldListFilter),
|
|
|
|
]
|
2023-12-04 13:08:36 -05:00
|
|
|
show_facets = admin.ShowFacets.ALWAYS
|
2023-04-10 13:12:45 -04:00
|
|
|
inlines = [WaiverAuditInline]
|
|
|
|
|
|
|
|
@admin.display(description="Latest Audit")
|
2023-04-10 14:29:12 -04:00
|
|
|
def latest_audit(self, obj: Waiver) -> WaiverAudit:
|
2023-04-10 13:12:45 -04:00
|
|
|
return obj.audits.latest()
|
2022-07-21 18:49:24 -04:00
|
|
|
|
2021-03-23 17:08:00 -04:00
|
|
|
|
|
|
|
admin.site.register(CmsRedRiverVeteransScholarship)
|