Adam Goldsmith
aef53b1828
parentheses are important! Also removed the superuser override, as that was hiding the issue for my account
387 lines
12 KiB
Python
387 lines
12 KiB
Python
from django.conf import settings
|
|
from django.contrib import staticfiles
|
|
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,
|
|
Count,
|
|
Exists,
|
|
Max,
|
|
OuterRef,
|
|
Q,
|
|
Subquery,
|
|
Value,
|
|
When,
|
|
)
|
|
from django.db.models.functions import Concat
|
|
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.views.generic import ListView
|
|
|
|
import django_tables2 as tables
|
|
import requests
|
|
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 .models import (
|
|
Certification,
|
|
CertificationVersion,
|
|
Department,
|
|
InstructorOrVendor,
|
|
Waiver,
|
|
)
|
|
|
|
WIKI_URL = settings.WIKI_URL
|
|
|
|
|
|
class MemberCertificationListView(ListView):
|
|
template_name = "paperwork/member_certifications.dj.html"
|
|
context_object_name = "certifications"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["show_outdated"] = (
|
|
self.request.GET.get("show_outdated", "false").lower() == "true"
|
|
)
|
|
return context
|
|
|
|
def get_queryset(self):
|
|
self.member = get_object_or_404(Member, uid=self.kwargs["uid"])
|
|
return Certification.objects.filter(member=self.member)
|
|
|
|
|
|
@login_required
|
|
def department_certifications(request):
|
|
if (member := Member.from_user(request.user)) is not None:
|
|
departments = Department.objects.filter_by_shop_lead(member)
|
|
else:
|
|
departments = []
|
|
|
|
certifications = Certification.objects.filter(
|
|
certification_version__definition__department__in=departments
|
|
).prefetch_related(
|
|
"certification_version__definition__department",
|
|
"member",
|
|
)
|
|
|
|
return render(
|
|
request,
|
|
"paperwork/department_certifications.dj.html",
|
|
{"departments": departments, "certifications": certifications},
|
|
)
|
|
|
|
|
|
def certification_pdf(request, cert_name):
|
|
wiki_page = f"{cert_name.replace('_', ' ')} Certification"
|
|
|
|
r = requests.get(
|
|
WIKI_URL + "/api.php",
|
|
params={
|
|
"action": "askargs",
|
|
"conditions": wiki_page,
|
|
"printouts": "Version|Approval Date|Approval status",
|
|
"format": "json",
|
|
"api_version": "2",
|
|
"origin": "*",
|
|
},
|
|
)
|
|
|
|
results = r.json()["query"]["results"]
|
|
if wiki_page not in results:
|
|
return HttpResponseNotFound(
|
|
f'No such certification found on wiki: <a href="{WIKI_URL}/wiki/{wiki_page}">{wiki_page}</a>'
|
|
)
|
|
|
|
printouts = results[wiki_page]["printouts"]
|
|
if printouts["Approval status"] != ["approve"]:
|
|
return HttpResponseBadRequest(
|
|
f'Certification is not yet approved on wiki: <a href="{WIKI_URL}/wiki/{wiki_page}">{wiki_page}</a>'
|
|
)
|
|
|
|
filename = (
|
|
f'{wiki_page}_v{printouts["Version"][0]} - {printouts["Approval Date"][0]}.pdf'
|
|
)
|
|
|
|
html = weasyprint.HTML(f"{WIKI_URL}/index.php?title={wiki_page}")
|
|
|
|
stylesheet = staticfiles.finders.find("paperwork/certification-print.css")
|
|
pdf = html.write_pdf(stylesheets=[stylesheet])
|
|
return HttpResponse(
|
|
pdf,
|
|
headers={
|
|
"Content-Type": "application/pdf",
|
|
"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):
|
|
def shop_error(access_field: str, shop_name: str):
|
|
member_list = CertificationVersion.objects.filter(
|
|
is_current=True,
|
|
definition__department__name=shop_name,
|
|
certification__member__pk__isnull=False,
|
|
).values("certification__member__pk")
|
|
return Case(
|
|
When(
|
|
Q(**{access_field: True}) & ~Q(uid__in=Subquery(member_list)),
|
|
Value("Has access but no cert"),
|
|
),
|
|
When(
|
|
Q(**{access_field: False}) & Q(uid__in=Subquery(member_list)),
|
|
Value("Has cert but no access"),
|
|
),
|
|
default=None,
|
|
)
|
|
|
|
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)
|
|
& ~(
|
|
Member.objects.has_flag("label", "Volunteer: Desker")
|
|
| Q(billing_method__startswith="Desker")
|
|
| Q(
|
|
Exists(
|
|
Department.objects.filter(
|
|
shop_lead_flag__members=OuterRef("pk")
|
|
)
|
|
)
|
|
)
|
|
),
|
|
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
|
|
|
|
|
|
class CertifiersTable(tables.Table):
|
|
certified_by = tables.Column()
|
|
certification_version__definition__name = tables.Column("Certification")
|
|
certification_version__definition__department__name = tables.Column("Department")
|
|
number_issued_on_this_tool = tables.Column()
|
|
last_issued_certification_date = tables.Column()
|
|
|
|
|
|
class CertifiersReport(
|
|
ExportMixin,
|
|
SingleTableMixin,
|
|
PermissionRequiredMixin,
|
|
ListView,
|
|
):
|
|
model = Certification
|
|
permission_required = "paperwork.view_certification"
|
|
template_name = "paperwork/certifiers_report.dj.html"
|
|
table_class = CertifiersTable
|
|
export_formats = ("csv", "xlsx", "ods")
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
return (
|
|
qs.values(
|
|
"certification_version__definition__department__name",
|
|
"certification_version__definition__name",
|
|
"certified_by",
|
|
)
|
|
.annotate(
|
|
number_issued_on_this_tool=Count("*"),
|
|
last_issued_certification_date=Max("date"),
|
|
)
|
|
.order_by(
|
|
"certification_version__definition__name",
|
|
"last_issued_certification_date",
|
|
)
|
|
)
|
|
|
|
|
|
class CertificationCountTable(tables.Table):
|
|
certification_version__definition__name = tables.Column("Certification")
|
|
certification_version__definition__department__name = tables.Column("Department")
|
|
total_issued = tables.Column()
|
|
|
|
|
|
class CertificationCountReport(
|
|
ExportMixin,
|
|
SingleTableMixin,
|
|
PermissionRequiredMixin,
|
|
ListView,
|
|
):
|
|
model = Certification
|
|
permission_required = "paperwork.view_certification"
|
|
template_name = "paperwork/certification_count_report.dj.html"
|
|
table_class = CertificationCountTable
|
|
export_formats = ("csv", "xlsx", "ods")
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
return (
|
|
qs.values(
|
|
"certification_version__definition__name",
|
|
"certification_version__definition__department__name",
|
|
)
|
|
.annotate(total_issued=Count("*"))
|
|
.order_by("certification_version__definition__name")
|
|
)
|