Adam Goldsmith
d6c2485d86
Moving from "Semantic Approved Revisions" to "Semantic Extra Special Properties", as the later is better supported
315 lines
10 KiB
Python
315 lines
10 KiB
Python
from typing import TYPE_CHECKING
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
from django.contrib.postgres.aggregates import StringAgg
|
|
from django.contrib.staticfiles import finders as staticfiles_finders
|
|
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 Cast, Concat
|
|
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.views.generic import ListView
|
|
|
|
import requests
|
|
import weasyprint
|
|
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,
|
|
)
|
|
from .tables import (
|
|
AccessVerificationTable,
|
|
CertificationCountTable,
|
|
CertifiersTable,
|
|
InstructorOrVendorTable,
|
|
WaiverReportTable,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterable
|
|
|
|
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):
|
|
departments: Iterable[Department]
|
|
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"] != ["approved"]:
|
|
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 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 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=StringAgg(
|
|
Cast("instructor_agreement_date", models.TextField()),
|
|
delimiter=", ",
|
|
distinct=True,
|
|
),
|
|
w9_date=StringAgg(
|
|
Cast("w9_date", models.TextField()), ", ", distinct=True
|
|
),
|
|
phone=StringAgg("phone", ", ", distinct=True),
|
|
email_address=StringAgg("email_address", ", ", distinct=True),
|
|
)
|
|
)
|
|
|
|
|
|
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 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 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")
|
|
)
|