2024-08-07 14:09:42 -04:00
|
|
|
from typing import TYPE_CHECKING
|
2024-05-04 16:38:51 -04:00
|
|
|
|
2022-07-20 13:55:27 -04:00
|
|
|
from django.conf import settings
|
2023-02-02 15:08:48 -05:00
|
|
|
from django.contrib.auth.decorators import login_required
|
2024-02-07 00:25:39 -05:00
|
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
2024-05-04 16:38:51 -04:00
|
|
|
from django.contrib.staticfiles import finders as staticfiles_finders
|
2024-02-07 21:03:19 -05:00
|
|
|
from django.db import models
|
2024-02-23 13:01:37 -05:00
|
|
|
from django.db.models import (
|
|
|
|
Case,
|
|
|
|
Count,
|
|
|
|
Exists,
|
|
|
|
Max,
|
|
|
|
OuterRef,
|
|
|
|
Q,
|
|
|
|
Subquery,
|
|
|
|
Value,
|
|
|
|
When,
|
|
|
|
)
|
2024-02-07 21:03:19 -05:00
|
|
|
from django.db.models.functions import Concat
|
2022-07-20 13:55:27 -04:00
|
|
|
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
2023-02-02 15:08:48 -05:00
|
|
|
from django.shortcuts import get_object_or_404, render
|
2022-02-15 21:43:19 -05:00
|
|
|
from django.views.generic import ListView
|
2021-03-23 17:08:00 -04:00
|
|
|
|
2022-07-20 13:55:27 -04:00
|
|
|
import requests
|
|
|
|
import weasyprint
|
2024-05-04 16:38:51 -04:00
|
|
|
from django_mysql.models.aggregates import GroupConcat
|
2024-02-07 00:25:39 -05:00
|
|
|
from django_tables2 import SingleTableMixin
|
|
|
|
from django_tables2.export.views import ExportMixin
|
2022-07-20 13:55:27 -04:00
|
|
|
|
2022-02-15 17:12:06 -05:00
|
|
|
from membershipworks.models import Member
|
2024-01-17 21:17:24 -05:00
|
|
|
|
2024-02-07 21:03:19 -05:00
|
|
|
from .models import (
|
|
|
|
Certification,
|
|
|
|
CertificationVersion,
|
|
|
|
Department,
|
|
|
|
InstructorOrVendor,
|
|
|
|
Waiver,
|
|
|
|
)
|
2024-04-18 11:30:18 -04:00
|
|
|
from .tables import (
|
|
|
|
AccessVerificationTable,
|
|
|
|
CertificationCountTable,
|
|
|
|
CertifiersTable,
|
|
|
|
InstructorOrVendorTable,
|
|
|
|
WaiverReportTable,
|
|
|
|
)
|
2022-02-15 17:12:06 -05:00
|
|
|
|
2024-08-07 14:09:42 -04:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from collections.abc import Iterable
|
|
|
|
|
2022-07-20 13:55:27 -04:00
|
|
|
WIKI_URL = settings.WIKI_URL
|
|
|
|
|
2022-02-15 17:12:06 -05:00
|
|
|
|
2022-02-15 21:43:19 -05:00
|
|
|
class MemberCertificationListView(ListView):
|
|
|
|
template_name = "paperwork/member_certifications.dj.html"
|
|
|
|
context_object_name = "certifications"
|
|
|
|
|
2022-11-07 13:49:39 -05:00
|
|
|
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
|
|
|
|
|
2022-02-15 21:43:19 -05:00
|
|
|
def get_queryset(self):
|
|
|
|
self.member = get_object_or_404(Member, uid=self.kwargs["uid"])
|
|
|
|
return Certification.objects.filter(member=self.member)
|
2022-07-20 13:55:27 -04:00
|
|
|
|
|
|
|
|
2023-02-02 15:08:48 -05:00
|
|
|
@login_required
|
|
|
|
def department_certifications(request):
|
2024-05-04 16:38:51 -04:00
|
|
|
departments: Iterable[Department]
|
2024-03-13 17:16:32 -04:00
|
|
|
if (member := Member.from_user(request.user)) is not None:
|
2023-04-25 23:23:27 -04:00
|
|
|
departments = Department.objects.filter_by_shop_lead(member)
|
2023-02-02 15:08:48 -05:00
|
|
|
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},
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-07-20 13:55:27 -04:00
|
|
|
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}")
|
|
|
|
|
2024-05-04 16:38:51 -04:00
|
|
|
stylesheet = staticfiles_finders.find("paperwork/certification-print.css")
|
2022-07-20 13:55:27 -04:00
|
|
|
pdf = html.write_pdf(stylesheets=[stylesheet])
|
|
|
|
return HttpResponse(
|
|
|
|
pdf,
|
|
|
|
headers={
|
|
|
|
"Content-Type": "application/pdf",
|
|
|
|
"Content-Disposition": f'inline; filename="{filename}"',
|
|
|
|
},
|
|
|
|
)
|
2024-02-07 00:25:39 -05:00
|
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
2024-02-07 21:03:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
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):
|
2024-02-23 13:01:37 -05:00
|
|
|
member_list = CertificationVersion.objects.filter(
|
|
|
|
is_current=True,
|
|
|
|
definition__department__name=shop_name,
|
|
|
|
certification__member__pk__isnull=False,
|
|
|
|
).values("certification__member__pk")
|
2024-02-07 21:03:19 -05:00
|
|
|
return Case(
|
|
|
|
When(
|
2024-02-23 13:01:37 -05:00
|
|
|
Q(**{access_field: True}) & ~Q(uid__in=Subquery(member_list)),
|
2024-02-07 21:03:19 -05:00
|
|
|
Value("Has access but no cert"),
|
|
|
|
),
|
|
|
|
When(
|
2024-02-23 13:01:37 -05:00
|
|
|
Q(**{access_field: False}) & Q(uid__in=Subquery(member_list)),
|
2024-02-07 21:03:19 -05:00
|
|
|
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)
|
2024-02-23 13:01:37 -05:00
|
|
|
& ~(
|
|
|
|
Member.objects.has_flag("label", "Volunteer: Desker")
|
|
|
|
| Q(billing_method__startswith="Desker")
|
|
|
|
| Q(
|
|
|
|
Exists(
|
|
|
|
Department.objects.filter(
|
|
|
|
shop_lead_flag__members=OuterRef("pk")
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
2024-02-07 21:03:19 -05:00
|
|
|
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
|
2024-02-16 11:35:12 -05:00
|
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
)
|