cmsmanage/paperwork/views.py

327 lines
10 KiB
Python
Raw Normal View History

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, Q, 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 request.user.is_superuser:
departments = Department.objects.prefetch_related(
"shop_lead_flag__members"
).all()
elif 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):
# 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