Compare commits
No commits in common. "bbd4d8d7003b80153d3486407f474491565bec1e" and "72a1ce8750ed74763bb38b26fd00bd6d64fae2dc" have entirely different histories.
bbd4d8d700
...
72a1ce8750
@ -41,7 +41,6 @@ INSTALLED_APPS = [
|
|||||||
"django_tables2",
|
"django_tables2",
|
||||||
"django_filters",
|
"django_filters",
|
||||||
"django_db_views",
|
"django_db_views",
|
||||||
"django_mysql",
|
|
||||||
"tasks.apps.TasksConfig",
|
"tasks.apps.TasksConfig",
|
||||||
"rentals.apps.RentalsConfig",
|
"rentals.apps.RentalsConfig",
|
||||||
"membershipworks.apps.MembershipworksConfig",
|
"membershipworks.apps.MembershipworksConfig",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import dataclasses
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
@ -11,16 +10,6 @@ def register(fragment):
|
|||||||
return fragment
|
return fragment
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class Link:
|
|
||||||
text: str
|
|
||||||
href: str
|
|
||||||
tooltip: str | None = None
|
|
||||||
body: str | None = None
|
|
||||||
_: dataclasses.KW_ONLY
|
|
||||||
permission: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class DashboardFragment:
|
class DashboardFragment:
|
||||||
name: str
|
name: str
|
||||||
template: str
|
template: str
|
||||||
@ -29,12 +18,3 @@ class DashboardFragment:
|
|||||||
|
|
||||||
def __init__(self, request: HttpRequest):
|
def __init__(self, request: HttpRequest):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
|
|
||||||
class LinksCardDashboardFragment(DashboardFragment):
|
|
||||||
template = "dashboard/links_card.dj.html"
|
|
||||||
links: [Link] = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def context(self):
|
|
||||||
return {"links": self.links}
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{{ app }}</h5>
|
<h5 class="card-header">{{ app }}</h5>
|
||||||
{% include app_dash.template with app=app ctx=app_dash.context %}
|
{% include app_dash.template with ctx=app_dash.context %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -24,9 +24,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block script %}
|
|
||||||
<script>
|
|
||||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
|
||||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -1,28 +1,7 @@
|
|||||||
<div class="accordion accordion-flush" id="{{ app|slugify }}-dash">
|
<ul class="list-group list-group-flush">
|
||||||
{% for link in ctx.links %}
|
{% for text, link in ctx.links.items %}
|
||||||
{% if link.permission is None or link.permission in perms %}
|
<li class="list-group-item">
|
||||||
<div class="accordion-item">
|
<a class="card-link" href="{{ link }}">{{ text }}</a>
|
||||||
<div class="accordion-header d-flex align-items-center">
|
</li>
|
||||||
<a {% if link.tooltip is not None %}data-bs-toggle="tooltip" title="{{ link.tooltip }}"{% endif %}
|
|
||||||
href="{{ link.href }}"
|
|
||||||
class="text-nowrap mx-3 p-1">{{ link.text }}</a>
|
|
||||||
{% if link.body is not None %}
|
|
||||||
<button class="accordion-button collapsed bg-transparent shadow-none py-1"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#{{ app|slugify }}-{{ forloop.counter }}"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-controls="{{ app|slugify }}-{{ forloop.counter }}"></button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if link.body is not None %}
|
|
||||||
<div id="{{ app|slugify }}-{{ forloop.counter }}"
|
|
||||||
class="accordion-collapse collapse"
|
|
||||||
data-bs-parent="#{{ app|slugify }}-dash">
|
|
||||||
<div class="accordion-body">{{ link.body }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</ul>
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
import dashboard
|
import dashboard
|
||||||
from dashboard import Link
|
|
||||||
|
|
||||||
from .views import REPORTS
|
from .views import REPORTS
|
||||||
|
|
||||||
|
|
||||||
@dashboard.register
|
@dashboard.register
|
||||||
class DoorControlDashboardFragment(dashboard.LinksCardDashboardFragment):
|
class DoorControlDashboardFragment(dashboard.DashboardFragment):
|
||||||
name = "Door Controls"
|
name = "Door Controls"
|
||||||
|
template = "dashboard/links_card.dj.html"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def links(self) -> list[Link]:
|
def context(self) -> Any:
|
||||||
return [
|
return {
|
||||||
Link(name, link, permission="doorcontrol.view_hidevent")
|
"links": dict(rt for report in REPORTS for rt in report._report_types())
|
||||||
for report in REPORTS
|
}
|
||||||
for name, link in report._report_types()
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visible(self) -> bool:
|
def visible(self) -> bool:
|
||||||
|
@ -1,31 +1,24 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
import dashboard
|
import dashboard
|
||||||
from dashboard import Link
|
|
||||||
|
|
||||||
|
|
||||||
@dashboard.register
|
@dashboard.register
|
||||||
class MembershipworksDashboardFragment(dashboard.LinksCardDashboardFragment):
|
class MembershipworksDashboardFragment(dashboard.DashboardFragment):
|
||||||
name = "MembershipWorks"
|
name = "MembershipWorks"
|
||||||
|
template = "dashboard/links_card.dj.html"
|
||||||
|
|
||||||
links = [
|
@property
|
||||||
Link(
|
def context(self) -> Any:
|
||||||
"Upcoming Events",
|
links = {}
|
||||||
reverse("membershipworks:upcoming-events"),
|
|
||||||
permission="membershipworks.view_event",
|
if self.request.user.has_perm("membershipworks.view_event"):
|
||||||
tooltip="Generator for Wordpress posts",
|
links["Event Report"] = reverse("membershipworks:event-index-report")
|
||||||
),
|
links["Event Attendees"] = reverse("membershipworks:event-attendees")
|
||||||
Link(
|
|
||||||
"Event Report",
|
return {"links": links}
|
||||||
reverse("membershipworks:event-index-report"),
|
|
||||||
permission="membershipworks.view_event",
|
|
||||||
),
|
|
||||||
Link(
|
|
||||||
"Event Attendees",
|
|
||||||
reverse("membershipworks:event-attendees"),
|
|
||||||
permission="membershipworks.view_event",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visible(self) -> bool:
|
def visible(self) -> bool:
|
||||||
|
@ -273,7 +273,7 @@ class Member(BaseModel):
|
|||||||
if hasattr(user, "ldap_user"):
|
if hasattr(user, "ldap_user"):
|
||||||
return cls.objects.get(uid=user.ldap_user.attrs["employeeNumber"][0])
|
return cls.objects.get(uid=user.ldap_user.attrs["employeeNumber"][0])
|
||||||
|
|
||||||
def sanitized_mailbox(self, use_volunteer=False) -> str:
|
def sanitized_mailbox(self, name_ext: str = "", use_volunteer=False) -> str:
|
||||||
if use_volunteer and self.volunteer_email:
|
if use_volunteer and self.volunteer_email:
|
||||||
email = self.volunteer_email
|
email = self.volunteer_email
|
||||||
elif self.email:
|
elif self.email:
|
||||||
@ -285,7 +285,7 @@ class Member(BaseModel):
|
|||||||
return email
|
return email
|
||||||
|
|
||||||
return django.core.mail.message.sanitize_address(
|
return django.core.mail.message.sanitize_address(
|
||||||
(self.account_name, email), settings.DEFAULT_CHARSET
|
(self.account_name + name_ext, email), settings.DEFAULT_CHARSET
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,7 +92,10 @@ class DepartmentViewSet(viewsets.ModelViewSet):
|
|||||||
# Add members to the Shop Leads mailing list, but don't configure it
|
# Add members to the Shop Leads mailing list, but don't configure it
|
||||||
lists["ShopLeads"] = {
|
lists["ShopLeads"] = {
|
||||||
"members": {
|
"members": {
|
||||||
shoplead.sanitized_mailbox(use_volunteer=True)
|
shoplead.sanitized_mailbox(
|
||||||
|
f" - {'/'.join(department.name for department in departments)}",
|
||||||
|
use_volunteer=True,
|
||||||
|
)
|
||||||
for shoplead, departments in shopleads.items()
|
for shoplead, departments in shopleads.items()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,61 +1,37 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
import dashboard
|
import dashboard
|
||||||
from dashboard import Link
|
|
||||||
from membershipworks.models import Member
|
from membershipworks.models import Member
|
||||||
|
|
||||||
from .models import Department
|
from .models import Department
|
||||||
|
|
||||||
|
|
||||||
@dashboard.register
|
@dashboard.register
|
||||||
class PaperworkDashboardFragment(dashboard.LinksCardDashboardFragment):
|
class PaperworkDashboardFragment(dashboard.DashboardFragment):
|
||||||
name = "Paperwork"
|
name = "Paperwork"
|
||||||
|
template = "dashboard/links_card.dj.html"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def links(self) -> list[Link]:
|
def context(self) -> Any:
|
||||||
links = [
|
links = {}
|
||||||
Link(
|
|
||||||
"Waivers",
|
|
||||||
reverse("paperwork:waivers-report"),
|
|
||||||
permission="paperwork.view_waiver",
|
|
||||||
),
|
|
||||||
Link(
|
|
||||||
"Instructors and Vendors",
|
|
||||||
reverse("paperwork:instructors-and-vendor-report"),
|
|
||||||
permission="paperwork.view_instructororvendor",
|
|
||||||
),
|
|
||||||
Link(
|
|
||||||
"Access Verification",
|
|
||||||
reverse("paperwork:access-verification-report"),
|
|
||||||
permission="paperwork.view_certification",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
member = Member.from_user(self.request.user)
|
member = Member.from_user(self.request.user)
|
||||||
if member is not None:
|
if member is not None:
|
||||||
links.append(
|
links["Member Certifications"] = reverse(
|
||||||
dashboard.Link(
|
"paperwork:member_certifications", kwargs={"uid": member.uid}
|
||||||
"Member Certifications",
|
|
||||||
reverse(
|
|
||||||
"paperwork:member_certifications", kwargs={"uid": member.uid}
|
|
||||||
),
|
|
||||||
permission=None,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.user.is_superuser or (
|
if self.request.user.is_superuser or (
|
||||||
member is not None
|
member is not None
|
||||||
and Department.objects.filter_by_shop_lead(member).exists()
|
and Department.objects.filter_by_shop_lead(member).exists()
|
||||||
):
|
):
|
||||||
links.append(
|
links["Department Certifications"] = reverse(
|
||||||
Link(
|
"paperwork:department_certifications"
|
||||||
"Department Certifications",
|
|
||||||
reverse("paperwork:department_certifications"),
|
|
||||||
permission=None,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return links
|
return {"links": links}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visible(self) -> bool:
|
def visible(self) -> bool:
|
||||||
|
@ -5,7 +5,8 @@ from typing import TYPE_CHECKING, TypedDict
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import OuterRef, Q, Subquery
|
from django.db.models import Q, Window
|
||||||
|
from django.db.models.functions import FirstValue
|
||||||
|
|
||||||
from django_stubs_ext import WithAnnotations
|
from django_stubs_ext import WithAnnotations
|
||||||
from semver import VersionInfo
|
from semver import VersionInfo
|
||||||
@ -141,18 +142,23 @@ class CertificationVersionAnnotations(TypedDict):
|
|||||||
|
|
||||||
class CertificationVersionManager(models.Manager["CertificationVersion"]):
|
class CertificationVersionManager(models.Manager["CertificationVersion"]):
|
||||||
def get_queryset(self) -> models.QuerySet["CertificationVersion"]:
|
def get_queryset(self) -> models.QuerySet["CertificationVersion"]:
|
||||||
qs = super().get_queryset()
|
window = {
|
||||||
department_versions = qs.filter(definition=OuterRef("definition")).order_by(
|
"partition_by": "definition",
|
||||||
"-major",
|
"order_by": [
|
||||||
"-minor",
|
"-major",
|
||||||
"-patch",
|
"-minor",
|
||||||
"-prerelease",
|
"-patch",
|
||||||
)
|
"-prerelease",
|
||||||
|
],
|
||||||
return qs.annotate(
|
}
|
||||||
is_latest=Q(pk=Subquery(department_versions.values("pk")[:1])),
|
return (
|
||||||
# TODO: should do a more correct comparison than just major version
|
super()
|
||||||
is_current=Q(major=Subquery(department_versions.values("major")[:1])),
|
.get_queryset()
|
||||||
|
.annotate(
|
||||||
|
is_latest=Q(pk=Window(FirstValue("pk"), **window)),
|
||||||
|
# TODO: should do a more correct comparison than just major version
|
||||||
|
is_current=Q(major=Window(FirstValue("major"), **window)),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.dj.html" %}
|
|
||||||
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block title %}Access Verification{% endblock %}
|
|
||||||
{% block admin_link %}
|
|
||||||
{% url 'admin:paperwork_certification_changelist' %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
{% include "cmsmanage/components/download_table.dj.html" %}
|
|
||||||
{% render_table table %}
|
|
||||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.dj.html" %}
|
|
||||||
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block title %}Instructors and Vendors{% endblock %}
|
|
||||||
{% block admin_link %}
|
|
||||||
{% url 'admin:paperwork_instructororvendor_changelist' %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
{% include "cmsmanage/components/download_table.dj.html" %}
|
|
||||||
{% render_table table %}
|
|
||||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.dj.html" %}
|
|
||||||
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block title %}Waiver Report{% endblock %}
|
|
||||||
{% block admin_link %}
|
|
||||||
{% url 'admin:paperwork_waiver_changelist' %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
{% include "cmsmanage/components/download_table.dj.html" %}
|
|
||||||
{% render_table table %}
|
|
||||||
{% endblock %}
|
|
@ -25,19 +25,4 @@ urlpatterns = [
|
|||||||
autocomplete_views.CertificationVersionAutocomplete.as_view(),
|
autocomplete_views.CertificationVersionAutocomplete.as_view(),
|
||||||
name="certification_version_autocomplete",
|
name="certification_version_autocomplete",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"waivers",
|
|
||||||
views.WaiverReport.as_view(),
|
|
||||||
name="waivers-report",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"instructors-and-vendors",
|
|
||||||
views.InstructorOrVendorReport.as_view(),
|
|
||||||
name="instructors-and-vendor-report",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"access-verification",
|
|
||||||
views.AccessVerificationReport.as_view(),
|
|
||||||
name="access-verification-report",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -1,30 +1,16 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import staticfiles
|
from django.contrib import staticfiles
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
||||||
from django.db import models
|
|
||||||
from django.db.models import Case, Q, Value, When
|
|
||||||
from django.db.models.functions import Concat
|
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
import django_tables2 as tables
|
|
||||||
import requests
|
import requests
|
||||||
import weasyprint
|
import weasyprint
|
||||||
from django_mysql.models import GroupConcat
|
|
||||||
from django_tables2 import SingleTableMixin
|
|
||||||
from django_tables2.export.views import ExportMixin
|
|
||||||
|
|
||||||
from membershipworks.models import Member
|
from membershipworks.models import Member
|
||||||
|
|
||||||
from .models import (
|
from .models import Certification, Department
|
||||||
Certification,
|
|
||||||
CertificationVersion,
|
|
||||||
Department,
|
|
||||||
InstructorOrVendor,
|
|
||||||
Waiver,
|
|
||||||
)
|
|
||||||
|
|
||||||
WIKI_URL = settings.WIKI_URL
|
WIKI_URL = settings.WIKI_URL
|
||||||
|
|
||||||
@ -112,215 +98,3 @@ def certification_pdf(request, cert_name):
|
|||||||
"Content-Disposition": f'inline; filename="{filename}"',
|
"Content-Disposition": f'inline; filename="{filename}"',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WarnEmptyColumn(tables.Column):
|
|
||||||
attrs = {
|
|
||||||
"td": {
|
|
||||||
"class": lambda value, bound_column: "table-danger"
|
|
||||||
if value == bound_column.default
|
|
||||||
else ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class WaiverReportTable(tables.Table):
|
|
||||||
emergency_contact_name = WarnEmptyColumn()
|
|
||||||
emergency_contact_number = WarnEmptyColumn()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Waiver
|
|
||||||
fields = [
|
|
||||||
"name",
|
|
||||||
"date",
|
|
||||||
"emergency_contact_name",
|
|
||||||
"emergency_contact_number",
|
|
||||||
"waiver_version",
|
|
||||||
"guardian_name",
|
|
||||||
"guardian_relation",
|
|
||||||
"guardian_date",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class WaiverReport(ExportMixin, SingleTableMixin, PermissionRequiredMixin, ListView):
|
|
||||||
permission_required = "paperwork.view_waiver"
|
|
||||||
template_name = "paperwork/waiver_report.dj.html"
|
|
||||||
queryset = Waiver.objects.order_by("name").all()
|
|
||||||
table_class = WaiverReportTable
|
|
||||||
table_pagination = False
|
|
||||||
|
|
||||||
|
|
||||||
class InstructorOrVendorTable(tables.Table):
|
|
||||||
instructor_agreement_date = WarnEmptyColumn(
|
|
||||||
"Instructor Agreement Date(s)", default="Missing"
|
|
||||||
)
|
|
||||||
w9_date = WarnEmptyColumn(default="Missing")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = InstructorOrVendor
|
|
||||||
fields = [
|
|
||||||
"name",
|
|
||||||
"instructor_agreement_date",
|
|
||||||
"w9_date",
|
|
||||||
"phone",
|
|
||||||
"email_address",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class InstructorOrVendorReport(
|
|
||||||
ExportMixin,
|
|
||||||
SingleTableMixin,
|
|
||||||
PermissionRequiredMixin,
|
|
||||||
ListView,
|
|
||||||
):
|
|
||||||
permission_required = "paperwork.view_instructororvendor"
|
|
||||||
template_name = "paperwork/instructor_or_vendor_report.dj.html"
|
|
||||||
queryset = InstructorOrVendor.objects.order_by("name").all()
|
|
||||||
table_class = InstructorOrVendorTable
|
|
||||||
export_formats = ("csv", "xlsx", "ods")
|
|
||||||
|
|
||||||
def get_table_data(self):
|
|
||||||
return (
|
|
||||||
super()
|
|
||||||
.get_table_data()
|
|
||||||
.values("name")
|
|
||||||
.annotate(
|
|
||||||
instructor_agreement_date=GroupConcat(
|
|
||||||
"instructor_agreement_date", distinct=True, ordering="asc"
|
|
||||||
),
|
|
||||||
w9_date=GroupConcat("w9_date", distinct=True, ordering="asc"),
|
|
||||||
phone=GroupConcat("phone", distinct=True, ordering="asc"),
|
|
||||||
email_address=GroupConcat(
|
|
||||||
"email_address", distinct=True, ordering="asc"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ShopAccessErrorColumn(tables.Column):
|
|
||||||
def td_class(value):
|
|
||||||
if value.startswith("Has access but"):
|
|
||||||
return "table-danger"
|
|
||||||
elif value.startswith("Has cert but"):
|
|
||||||
return "table-warning"
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
attrs = {"td": {"class": td_class}}
|
|
||||||
|
|
||||||
|
|
||||||
class AccessVerificationTable(tables.Table):
|
|
||||||
account_name = tables.Column()
|
|
||||||
access_card = tables.Column()
|
|
||||||
billing_method = tables.Column()
|
|
||||||
join_date = tables.DateColumn()
|
|
||||||
renewal_date = tables.DateColumn()
|
|
||||||
access_front_door = tables.BooleanColumn(verbose_name="Front Door")
|
|
||||||
access_studio_space = tables.BooleanColumn(verbose_name="Studio Space")
|
|
||||||
wood_shop_error = ShopAccessErrorColumn()
|
|
||||||
metal_shop_error = ShopAccessErrorColumn()
|
|
||||||
extended_hours_error = ShopAccessErrorColumn()
|
|
||||||
extended_hours_shops_error = ShopAccessErrorColumn()
|
|
||||||
storage_closet_error = ShopAccessErrorColumn()
|
|
||||||
|
|
||||||
|
|
||||||
class AccessVerificationReport(
|
|
||||||
ExportMixin,
|
|
||||||
SingleTableMixin,
|
|
||||||
PermissionRequiredMixin,
|
|
||||||
ListView,
|
|
||||||
):
|
|
||||||
permission_required = "paperwork.view_certification"
|
|
||||||
template_name = "paperwork/access_verification_report.dj.html"
|
|
||||||
table_class = AccessVerificationTable
|
|
||||||
export_formats = ("csv", "xlsx", "ods")
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
# TODO: could be done with subqueries if membershipworks was not a separate DB
|
|
||||||
def shop_error(access_field: str, shop_name: str):
|
|
||||||
member_list = list(
|
|
||||||
CertificationVersion.objects.filter(
|
|
||||||
is_current=True,
|
|
||||||
definition__department__name=shop_name,
|
|
||||||
certification__member__pk__isnull=False,
|
|
||||||
)
|
|
||||||
.values_list(
|
|
||||||
"certification__member__pk",
|
|
||||||
flat=True,
|
|
||||||
)
|
|
||||||
.distinct()
|
|
||||||
)
|
|
||||||
return Case(
|
|
||||||
When(
|
|
||||||
Q(**{access_field: True}) & ~Q(uid__in=member_list),
|
|
||||||
Value("Has access but no cert"),
|
|
||||||
),
|
|
||||||
When(
|
|
||||||
Q(**{access_field: False}) & Q(uid__in=member_list),
|
|
||||||
Value("Has cert but no access"),
|
|
||||||
),
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: could be a lot cleaner if membershipworks was not a separate DB
|
|
||||||
storage_closet_members = (
|
|
||||||
Member.objects.filter(
|
|
||||||
Member.objects.has_flag("label", "Volunteer: Desker")
|
|
||||||
| Q(billing_method__startswith="Desker")
|
|
||||||
)
|
|
||||||
.union(
|
|
||||||
*[
|
|
||||||
department.shop_lead_flag.members.all()
|
|
||||||
for department in Department.objects.filter(
|
|
||||||
shop_lead_flag__isnull=False
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
.values_list("pk", flat=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
qs = (
|
|
||||||
Member.objects.with_is_active()
|
|
||||||
.filter(is_active=True)
|
|
||||||
.values(
|
|
||||||
"account_name",
|
|
||||||
"billing_method",
|
|
||||||
"join_date",
|
|
||||||
"renewal_date",
|
|
||||||
"access_front_door",
|
|
||||||
"access_studio_space",
|
|
||||||
access_card=Concat(
|
|
||||||
"access_card_facility_code",
|
|
||||||
Value("-", models.TextField()),
|
|
||||||
"access_card_number",
|
|
||||||
),
|
|
||||||
wood_shop_error=shop_error("access_wood_shop", "Wood Shop"),
|
|
||||||
metal_shop_error=shop_error("access_metal_shop", "Metal Shop"),
|
|
||||||
extended_hours_error=shop_error(
|
|
||||||
"access_front_door_and_studio_space_during_extended_hours",
|
|
||||||
"Closure/Lock-Up",
|
|
||||||
),
|
|
||||||
extended_hours_shops_error=shop_error(
|
|
||||||
"access_permitted_shops_during_extended_hours",
|
|
||||||
"Closure/Lock-Up",
|
|
||||||
),
|
|
||||||
storage_closet_error=Case(
|
|
||||||
When(
|
|
||||||
Q(access_storage_closet=True)
|
|
||||||
& ~Q(uid__in=storage_closet_members),
|
|
||||||
Value("Has access but not shop lead or desker"),
|
|
||||||
),
|
|
||||||
default=None,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
Q(access_front_door=False)
|
|
||||||
| Q(access_studio_space=False)
|
|
||||||
| Q(wood_shop_error__isnull=False)
|
|
||||||
| Q(metal_shop_error__isnull=False)
|
|
||||||
| Q(extended_hours_error__isnull=False)
|
|
||||||
| Q(extended_hours_shops_error__isnull=False)
|
|
||||||
| Q(storage_closet_error__isnull=False)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return qs
|
|
||||||
|
15
pdm.lock
generated
15
pdm.lock
generated
@ -5,7 +5,7 @@
|
|||||||
groups = ["default", "debug", "lint", "server", "typing", "dev"]
|
groups = ["default", "debug", "lint", "server", "typing", "dev"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4.1"
|
lock_version = "4.4.1"
|
||||||
content_hash = "sha256:88502778249494bd3f6fd407d51671f4e465065ec58427b60993d91d0808bdfd"
|
content_hash = "sha256:e647dfe717684c87b3a51bc1738ae7eb3d370ada3908a48c5ead3547c36a78d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohttp"
|
name = "aiohttp"
|
||||||
@ -499,19 +499,6 @@ files = [
|
|||||||
{file = "django_markdownx-4.0.7-py2.py3-none-any.whl", hash = "sha256:c1975ae3053481d4c111abd38997a5b5bb89235a1e3215f995d835942925fe7b"},
|
{file = "django_markdownx-4.0.7-py2.py3-none-any.whl", hash = "sha256:c1975ae3053481d4c111abd38997a5b5bb89235a1e3215f995d835942925fe7b"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "django-mysql"
|
|
||||||
version = "4.12.0"
|
|
||||||
requires_python = ">=3.8"
|
|
||||||
summary = "Django-MySQL extends Django's built-in MySQL and MariaDB support their specific features not available on other databases."
|
|
||||||
dependencies = [
|
|
||||||
"Django>=3.2",
|
|
||||||
]
|
|
||||||
files = [
|
|
||||||
{file = "django_mysql-4.12.0-py3-none-any.whl", hash = "sha256:1c188ee3a92590da21ce23351c309bb02df3b6611926521d312a9cdf6373c3d0"},
|
|
||||||
{file = "django_mysql-4.12.0.tar.gz", hash = "sha256:9a29b69ad30c85362984903379783b53731ee7b0cef4dfb4eb078f80e24f26d4"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-nh3"
|
name = "django-nh3"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -33,7 +33,6 @@ dependencies = [
|
|||||||
"tablib[ods,xlsx]~=3.5",
|
"tablib[ods,xlsx]~=3.5",
|
||||||
"django-filter~=23.5",
|
"django-filter~=23.5",
|
||||||
"django-db-views~=0.1",
|
"django-db-views~=0.1",
|
||||||
"django-mysql~=4.12",
|
|
||||||
]
|
]
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
@ -129,7 +128,7 @@ dev = [
|
|||||||
|
|
||||||
[tool.pdm.scripts]
|
[tool.pdm.scripts]
|
||||||
start = "./manage.py runserver"
|
start = "./manage.py runserver"
|
||||||
fmt.shell = "ruff check --fix && ruff format . && djlint --reformat ."
|
fmt.shell = "black . && djlint --reformat ."
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["pdm-backend"]
|
requires = ["pdm-backend"]
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
import dashboard
|
import dashboard
|
||||||
from dashboard import Link
|
|
||||||
|
|
||||||
|
|
||||||
@dashboard.register
|
@dashboard.register
|
||||||
class RentalsDashboardFragment(dashboard.LinksCardDashboardFragment):
|
class RentalsDashboardFragment(dashboard.DashboardFragment):
|
||||||
name = "Rentals"
|
name = "Rentals"
|
||||||
template = "dashboard/links_card.dj.html"
|
template = "dashboard/links_card.dj.html"
|
||||||
|
|
||||||
links = [Link("Locker Index", reverse("rentals:index"), permission=None)]
|
@property
|
||||||
|
def context(self) -> Any:
|
||||||
|
return {"links": {"Locker Index": reverse("rentals:index")}}
|
||||||
|
Loading…
Reference in New Issue
Block a user