cmsmanage/paperwork/views.py

313 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.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 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_mysql.models.aggregates 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,
)
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"] != ["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 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=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 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")
)