Compare commits
No commits in common. "7c26cf252db2bfe1c3510ae93a517c29e216f730" and "dd6ef3522337f3ea2f9d4c24d76e57d0546affa4" have entirely different histories.
7c26cf252d
...
dd6ef35223
@ -1,34 +0,0 @@
|
|||||||
name: Test
|
|
||||||
on: [ push, pull_request ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
DJANGO_SETTINGS_MODULE: cmsmanage.settings.ci
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: catthehacker/ubuntu:act-latest
|
|
||||||
services:
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:latest
|
|
||||||
env:
|
|
||||||
MARIADB_ROOT_PASSWORD: whatever
|
|
||||||
ports:
|
|
||||||
- 3306:3306
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized"]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Setup PDM
|
|
||||||
uses: pdm-project/setup-pdm@v4
|
|
||||||
with:
|
|
||||||
cache: true
|
|
||||||
python-version: ~3.11
|
|
||||||
- name: Install apt dependencies
|
|
||||||
run: >-
|
|
||||||
sudo apt-get update &&
|
|
||||||
sudo apt-get -y install build-essential python3-dev libldap2-dev libsasl2-dev mariadb-client
|
|
||||||
- name: Install python dependencies
|
|
||||||
run: pdm install
|
|
||||||
- name: Run tests
|
|
||||||
run: pdm run ./manage.py test --parallel auto
|
|
@ -141,4 +141,3 @@ Q_CLUSTER = {
|
|||||||
|
|
||||||
# Django-Tables2
|
# Django-Tables2
|
||||||
DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap5-responsive.html"
|
DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap5-responsive.html"
|
||||||
DJANGO_TABLES2_TABLE_ATTRS = {"class": "table mx-auto w-auto"}
|
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
from .base import * # noqa: F403
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = "aed7jee2kai1we9eithae0gaegh9ohthoh4phahk5bau4Ahxaijo3aicheex3qua"
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.mysql",
|
|
||||||
"HOST": "mariadb",
|
|
||||||
"NAME": "CMS_Database",
|
|
||||||
"USER": "root",
|
|
||||||
"PASSWORD": "whatever",
|
|
||||||
"OPTIONS": {
|
|
||||||
"charset": "utf8mb4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"membershipworks": {
|
|
||||||
"ENGINE": "django.db.backends.mysql",
|
|
||||||
"HOST": "mariadb",
|
|
||||||
"NAME": "membershipworks",
|
|
||||||
"USER": "root",
|
|
||||||
"PASSWORD": "whatever",
|
|
||||||
"OPTIONS": {
|
|
||||||
"charset": "utf8mb4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
@ -92,6 +92,7 @@ class Migration(migrations.Migration):
|
|||||||
options={
|
options={
|
||||||
"db_table": "hidevent",
|
"db_table": "hidevent",
|
||||||
"ordering": ("-timestamp",),
|
"ordering": ("-timestamp",),
|
||||||
|
"managed": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddConstraint(
|
migrations.AddConstraint(
|
||||||
|
@ -25,11 +25,6 @@ class MembershipworksDashboardFragment(dashboard.LinksCardDashboardFragment):
|
|||||||
reverse("membershipworks:event-attendees"),
|
reverse("membershipworks:event-attendees"),
|
||||||
permission="membershipworks.view_event",
|
permission="membershipworks.view_event",
|
||||||
),
|
),
|
||||||
Link(
|
|
||||||
"Missing Paperwork",
|
|
||||||
reverse("membershipworks:missing-paperwork-report"),
|
|
||||||
permission="membershipworks.view_member",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.dj.html" %}
|
|
||||||
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block title %}Missing Paperwork Report{% endblock %}
|
|
||||||
{% block admin_link %}
|
|
||||||
{% url 'admin:membershipworks_member_changelist' %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
{% include "cmsmanage/components/download_table.dj.html" %}
|
|
||||||
{% render_table table %}
|
|
||||||
{% endblock %}
|
|
@ -1,48 +1,51 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from .views import (
|
||||||
|
EventAttendeeListView,
|
||||||
|
EventIndexReport,
|
||||||
|
EventInvoiceView,
|
||||||
|
EventMonthReport,
|
||||||
|
EventYearReport,
|
||||||
|
MemberAutocomplete,
|
||||||
|
upcoming_events,
|
||||||
|
)
|
||||||
|
|
||||||
app_name = "membershipworks"
|
app_name = "membershipworks"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
||||||
"member-autocomplete/",
|
"member-autocomplete/",
|
||||||
views.MemberAutocomplete.as_view(),
|
MemberAutocomplete.as_view(),
|
||||||
name="member-autocomplete",
|
name="member-autocomplete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"upcoming-events/",
|
"upcoming-events/",
|
||||||
views.upcoming_events,
|
upcoming_events,
|
||||||
name="upcoming-events",
|
name="upcoming-events",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"event-report/",
|
"event-report/",
|
||||||
views.EventIndexReport.as_view(),
|
EventIndexReport.as_view(),
|
||||||
name="event-index-report",
|
name="event-index-report",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"event-report/<int:year>/",
|
"event-report/<int:year>/",
|
||||||
views.EventYearReport.as_view(),
|
EventYearReport.as_view(),
|
||||||
name="event-year-report",
|
name="event-year-report",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"event-report/<int:year>/<int:month>/",
|
"event-report/<int:year>/<int:month>/",
|
||||||
views.EventMonthReport.as_view(month_format="%m"),
|
EventMonthReport.as_view(month_format="%m"),
|
||||||
name="event-month-report",
|
name="event-month-report",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"event-invoice/<eid>",
|
"event-invoice/<eid>",
|
||||||
views.EventInvoiceView.as_view(),
|
EventInvoiceView.as_view(),
|
||||||
name="event-invoice",
|
name="event-invoice",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"event-attendees",
|
"event-attendees",
|
||||||
views.EventAttendeeListView.as_view(),
|
EventAttendeeListView.as_view(),
|
||||||
name="event-attendees",
|
name="event-attendees",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"missing-paperwork",
|
|
||||||
views.MissingPaperworkReport.as_view(),
|
|
||||||
name="missing-paperwork-report",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -4,7 +4,7 @@ from django.conf import settings
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.db.models import OuterRef, Q, Subquery
|
from django.db.models import Subquery
|
||||||
from django.db.models.functions import TruncMonth, TruncYear
|
from django.db.models.functions import TruncMonth, TruncYear
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.template.defaultfilters import floatformat
|
from django.template.defaultfilters import floatformat
|
||||||
@ -19,7 +19,6 @@ import django_filters
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
from django_filters.views import BaseFilterView
|
from django_filters.views import BaseFilterView
|
||||||
from django_mysql.models import GroupConcat
|
|
||||||
from django_tables2 import A, SingleTableMixin
|
from django_tables2 import A, SingleTableMixin
|
||||||
from django_tables2.export.views import ExportMixin
|
from django_tables2.export.views import ExportMixin
|
||||||
|
|
||||||
@ -366,53 +365,3 @@ class EventAttendeeListView(
|
|||||||
|
|
||||||
def get_table_data(self):
|
def get_table_data(self):
|
||||||
return super().get_table_data().values("name", "email").distinct()
|
return super().get_table_data().values("name", "email").distinct()
|
||||||
|
|
||||||
|
|
||||||
class MissingPaperworkTable(tables.Table):
|
|
||||||
policy_agreement = tables.BooleanColumn()
|
|
||||||
authorize_charge = tables.BooleanColumn()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Member
|
|
||||||
fields = [
|
|
||||||
"first_name",
|
|
||||||
"last_name",
|
|
||||||
"membership",
|
|
||||||
"billing_method",
|
|
||||||
"join_date",
|
|
||||||
"membership_agreement_signed_and_on_file_date",
|
|
||||||
"waiver_form_signed_and_on_file_date",
|
|
||||||
"policy_agreement",
|
|
||||||
"authorize_charge",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MissingPaperworkReport(
|
|
||||||
ExportMixin,
|
|
||||||
SingleTableMixin,
|
|
||||||
PermissionRequiredMixin,
|
|
||||||
ListView,
|
|
||||||
):
|
|
||||||
model = Member
|
|
||||||
permission_required = "membershipworks.view_member"
|
|
||||||
template_name = "membershipworks/missing_paperwork_report.dj.html"
|
|
||||||
table_class = MissingPaperworkTable
|
|
||||||
export_formats = ("csv", "xlsx", "ods")
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
qs = super().get_queryset()
|
|
||||||
return (
|
|
||||||
qs.with_is_active()
|
|
||||||
.filter(
|
|
||||||
Q(membership_agreement_signed_and_on_file_date__isnull=True)
|
|
||||||
| Q(waiver_form_signed_and_on_file_date__isnull=True),
|
|
||||||
is_active=True,
|
|
||||||
)
|
|
||||||
.annotate(
|
|
||||||
membership=Subquery(
|
|
||||||
qs.filter(
|
|
||||||
pk=OuterRef("pk"), flags__type__in=("level", "addon")
|
|
||||||
).values(m=GroupConcat("flags__name"))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
@ -29,16 +29,6 @@ class PaperworkDashboardFragment(dashboard.LinksCardDashboardFragment):
|
|||||||
reverse("paperwork:access-verification-report"),
|
reverse("paperwork:access-verification-report"),
|
||||||
permission="paperwork.view_certification",
|
permission="paperwork.view_certification",
|
||||||
),
|
),
|
||||||
Link(
|
|
||||||
"Certifiers",
|
|
||||||
reverse("paperwork:certifiers-report"),
|
|
||||||
permission="paperwork.view_certification",
|
|
||||||
),
|
|
||||||
Link(
|
|
||||||
"Certification Count",
|
|
||||||
reverse("paperwork:certification-count-report"),
|
|
||||||
permission="paperwork.view_certification",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
member = Member.from_user(self.request.user)
|
member = Member.from_user(self.request.user)
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.dj.html" %}
|
|
||||||
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block title %}Certification Count Report{% endblock %}
|
|
||||||
{% block admin_link %}
|
|
||||||
{% url 'admin:paperwork_certification_changelist' %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
{% include "cmsmanage/components/download_table.dj.html" %}
|
|
||||||
{% render_table table %}
|
|
||||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.dj.html" %}
|
|
||||||
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block title %}Certifiers Report{% endblock %}
|
|
||||||
{% block admin_link %}
|
|
||||||
{% url 'admin:paperwork_certification_changelist' %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
{% include "cmsmanage/components/download_table.dj.html" %}
|
|
||||||
{% render_table table %}
|
|
||||||
{% endblock %}
|
|
@ -1,130 +1 @@
|
|||||||
from django.contrib.auth import get_user_model
|
# Create your tests here.
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.test import Client
|
|
||||||
|
|
||||||
from hypothesis import given
|
|
||||||
from hypothesis import strategies as st
|
|
||||||
from hypothesis.extra.django import TestCase, from_model
|
|
||||||
|
|
||||||
from paperwork.models import (
|
|
||||||
Certification,
|
|
||||||
CertificationDefinition,
|
|
||||||
CertificationVersion,
|
|
||||||
Department,
|
|
||||||
InstructorOrVendor,
|
|
||||||
Waiver,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionRequiredViewTestCaseMixin:
|
|
||||||
permissions = []
|
|
||||||
path = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
User = get_user_model()
|
|
||||||
cls.client = Client()
|
|
||||||
|
|
||||||
cls.user_without_permission = User.objects.create_user(
|
|
||||||
username="user_without_permission"
|
|
||||||
)
|
|
||||||
cls.user_with_permission = User.objects.create_user(
|
|
||||||
username="user_with_permission"
|
|
||||||
)
|
|
||||||
|
|
||||||
resolved_permissions = [
|
|
||||||
Permission.objects.get(
|
|
||||||
content_type=ContentType.objects.get_for_model(permission["model"]),
|
|
||||||
codename=permission["codename"],
|
|
||||||
)
|
|
||||||
for permission in cls.permissions
|
|
||||||
]
|
|
||||||
|
|
||||||
cls.user_with_permission.user_permissions.add(*resolved_permissions)
|
|
||||||
|
|
||||||
def test_missing_permission(self) -> None:
|
|
||||||
self.client.force_login(self.user_without_permission)
|
|
||||||
response = self.client.get(self.path)
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
|
|
||||||
class WaiverReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase):
|
|
||||||
permissions = [{"model": Waiver, "codename": "view_waiver"}]
|
|
||||||
path = "/paperwork/waivers"
|
|
||||||
|
|
||||||
@given(waivers=st.lists(from_model(Waiver, number=st.none())))
|
|
||||||
def test_waiver_report(self, waivers: list[Waiver]) -> None:
|
|
||||||
self.client.force_login(self.user_with_permission)
|
|
||||||
response = self.client.get(self.path)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
|
|
||||||
class InstructorOrVendorReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase):
|
|
||||||
permissions = [{"model": InstructorOrVendor, "codename": "view_instructororvendor"}]
|
|
||||||
path = "/paperwork/instructors-and-vendors"
|
|
||||||
|
|
||||||
@given(
|
|
||||||
instructors_or_vendors=st.lists(
|
|
||||||
from_model(InstructorOrVendor, serial=st.none())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def test_waiver_report(
|
|
||||||
self, instructors_or_vendors: list[InstructorOrVendor]
|
|
||||||
) -> None:
|
|
||||||
self.client.force_login(self.user_with_permission)
|
|
||||||
response = self.client.get(self.path)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def random_certifications(draw):
|
|
||||||
departments = draw(st.lists(from_model(Department), min_size=1))
|
|
||||||
definitions = draw(
|
|
||||||
st.lists(
|
|
||||||
from_model(
|
|
||||||
CertificationDefinition, department=st.sampled_from(departments)
|
|
||||||
),
|
|
||||||
min_size=1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
certification_versions = draw(
|
|
||||||
st.lists(
|
|
||||||
from_model(CertificationVersion, definition=st.sampled_from(definitions)),
|
|
||||||
min_size=1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return draw(
|
|
||||||
st.lists(
|
|
||||||
from_model(
|
|
||||||
Certification,
|
|
||||||
number=st.none(),
|
|
||||||
certification_version=st.sampled_from(certification_versions),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertifiersReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase):
|
|
||||||
permissions = [{"model": Certification, "codename": "view_certification"}]
|
|
||||||
path = "/paperwork/certifiers"
|
|
||||||
|
|
||||||
@given(certifications=random_certifications())
|
|
||||||
def test_certifers_report(self, certifications: list[Certification]) -> None:
|
|
||||||
self.client.force_login(self.user_with_permission)
|
|
||||||
response = self.client.get(self.path)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
|
|
||||||
class CertificationCountReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase):
|
|
||||||
permissions = [{"model": Certification, "codename": "view_certification"}]
|
|
||||||
path = "/paperwork/certification-count"
|
|
||||||
|
|
||||||
@given(certifications=random_certifications())
|
|
||||||
def test_certification_count_report(
|
|
||||||
self, certifications: list[Certification]
|
|
||||||
) -> None:
|
|
||||||
self.client.force_login(self.user_with_permission)
|
|
||||||
response = self.client.get(self.path)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
@ -40,14 +40,4 @@ urlpatterns = [
|
|||||||
views.AccessVerificationReport.as_view(),
|
views.AccessVerificationReport.as_view(),
|
||||||
name="access-verification-report",
|
name="access-verification-report",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"certifiers",
|
|
||||||
views.CertifiersReport.as_view(),
|
|
||||||
name="certifiers-report",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"certification-count",
|
|
||||||
views.CertificationCountReport.as_view(),
|
|
||||||
name="certification-count-report",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -3,7 +3,7 @@ from django.contrib import staticfiles
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Case, Count, Max, Q, Value, When
|
from django.db.models import Case, Q, Value, When
|
||||||
from django.db.models.functions import Concat
|
from django.db.models.functions import Concat
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
@ -324,72 +324,3 @@ class AccessVerificationReport(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return qs
|
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")
|
|
||||||
)
|
|
||||||
|
50
pdm.lock
50
pdm.lock
@ -5,7 +5,7 @@
|
|||||||
groups = ["default", "debug", "lint", "server", "typing", "dev"]
|
groups = ["default", "debug", "lint", "server", "typing", "dev"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4.1"
|
lock_version = "4.4.1"
|
||||||
content_hash = "sha256:379d72c4be2ef3f09d6a3f9cf0517f784fa18df7367386dfc09cd40fa521a25f"
|
content_hash = "sha256:88502778249494bd3f6fd407d51671f4e465065ec58427b60993d91d0808bdfd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohttp"
|
name = "aiohttp"
|
||||||
@ -908,35 +908,6 @@ files = [
|
|||||||
{file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"},
|
{file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hypothesis"
|
|
||||||
version = "6.98.5"
|
|
||||||
requires_python = ">=3.8"
|
|
||||||
summary = "A library for property-based testing"
|
|
||||||
dependencies = [
|
|
||||||
"attrs>=22.2.0",
|
|
||||||
"sortedcontainers<3.0.0,>=2.1.0",
|
|
||||||
]
|
|
||||||
files = [
|
|
||||||
{file = "hypothesis-6.98.5-py3-none-any.whl", hash = "sha256:9449b9878116133269da4941b6a20e83003ef95503a2106365d4756ef3adc2b7"},
|
|
||||||
{file = "hypothesis-6.98.5.tar.gz", hash = "sha256:cfe4c2320580f97dd0d11cd3ee954a347764aec42aa0c95b7a0285c2b02447ab"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hypothesis"
|
|
||||||
version = "6.98.5"
|
|
||||||
extras = ["django"]
|
|
||||||
requires_python = ">=3.8"
|
|
||||||
summary = "A library for property-based testing"
|
|
||||||
dependencies = [
|
|
||||||
"django>=3.2",
|
|
||||||
"hypothesis==6.98.5",
|
|
||||||
]
|
|
||||||
files = [
|
|
||||||
{file = "hypothesis-6.98.5-py3-none-any.whl", hash = "sha256:9449b9878116133269da4941b6a20e83003ef95503a2106365d4756ef3adc2b7"},
|
|
||||||
{file = "hypothesis-6.98.5.tar.gz", hash = "sha256:cfe4c2320580f97dd0d11cd3ee954a347764aec42aa0c95b7a0285c2b02447ab"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.4"
|
version = "3.4"
|
||||||
@ -1710,15 +1681,6 @@ files = [
|
|||||||
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sortedcontainers"
|
|
||||||
version = "2.4.0"
|
|
||||||
summary = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
|
|
||||||
files = [
|
|
||||||
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
|
|
||||||
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "soupsieve"
|
name = "soupsieve"
|
||||||
version = "2.3.2.post1"
|
version = "2.3.2.post1"
|
||||||
@ -1779,16 +1741,6 @@ files = [
|
|||||||
{file = "tablib-3.5.0.tar.gz", hash = "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33"},
|
{file = "tablib-3.5.0.tar.gz", hash = "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tblib"
|
|
||||||
version = "3.0.0"
|
|
||||||
requires_python = ">=3.8"
|
|
||||||
summary = "Traceback serialization library."
|
|
||||||
files = [
|
|
||||||
{file = "tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"},
|
|
||||||
{file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinycss2"
|
name = "tinycss2"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
@ -125,8 +125,6 @@ debug = [
|
|||||||
dev = [
|
dev = [
|
||||||
"django-extensions~=3.2",
|
"django-extensions~=3.2",
|
||||||
"ipython~=8.21",
|
"ipython~=8.21",
|
||||||
"hypothesis[django]~=6.98",
|
|
||||||
"tblib~=3.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.pdm.scripts]
|
[tool.pdm.scripts]
|
||||||
|
Loading…
Reference in New Issue
Block a user