Compare commits
8 Commits
dd6ef35223
...
7c26cf252d
Author | SHA1 | Date | |
---|---|---|---|
7c26cf252d | |||
9568a32b00 | |||
d59318e2a5 | |||
f9fdd7d549 | |||
6c5e55507e | |||
a660a73e96 | |||
b3e3de15a2 | |||
4934abccb8 |
34
.gitea/workflows/test.yml
Normal file
34
.gitea/workflows/test.yml
Normal file
@ -0,0 +1,34 @@
|
||||
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,3 +141,4 @@ Q_CLUSTER = {
|
||||
|
||||
# Django-Tables2
|
||||
DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap5-responsive.html"
|
||||
DJANGO_TABLES2_TABLE_ATTRS = {"class": "table mx-auto w-auto"}
|
||||
|
33
cmsmanage/settings/ci.py
Normal file
33
cmsmanage/settings/ci.py
Normal file
@ -0,0 +1,33 @@
|
||||
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,7 +92,6 @@ class Migration(migrations.Migration):
|
||||
options={
|
||||
"db_table": "hidevent",
|
||||
"ordering": ("-timestamp",),
|
||||
"managed": False,
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
|
@ -25,6 +25,11 @@ class MembershipworksDashboardFragment(dashboard.LinksCardDashboardFragment):
|
||||
reverse("membershipworks:event-attendees"),
|
||||
permission="membershipworks.view_event",
|
||||
),
|
||||
Link(
|
||||
"Missing Paperwork",
|
||||
reverse("membershipworks:missing-paperwork-report"),
|
||||
permission="membershipworks.view_member",
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
|
@ -0,0 +1,12 @@
|
||||
{% 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,51 +1,48 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import (
|
||||
EventAttendeeListView,
|
||||
EventIndexReport,
|
||||
EventInvoiceView,
|
||||
EventMonthReport,
|
||||
EventYearReport,
|
||||
MemberAutocomplete,
|
||||
upcoming_events,
|
||||
)
|
||||
from . import views
|
||||
|
||||
app_name = "membershipworks"
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"member-autocomplete/",
|
||||
MemberAutocomplete.as_view(),
|
||||
views.MemberAutocomplete.as_view(),
|
||||
name="member-autocomplete",
|
||||
),
|
||||
path(
|
||||
"upcoming-events/",
|
||||
upcoming_events,
|
||||
views.upcoming_events,
|
||||
name="upcoming-events",
|
||||
),
|
||||
path(
|
||||
"event-report/",
|
||||
EventIndexReport.as_view(),
|
||||
views.EventIndexReport.as_view(),
|
||||
name="event-index-report",
|
||||
),
|
||||
path(
|
||||
"event-report/<int:year>/",
|
||||
EventYearReport.as_view(),
|
||||
views.EventYearReport.as_view(),
|
||||
name="event-year-report",
|
||||
),
|
||||
path(
|
||||
"event-report/<int:year>/<int:month>/",
|
||||
EventMonthReport.as_view(month_format="%m"),
|
||||
views.EventMonthReport.as_view(month_format="%m"),
|
||||
name="event-month-report",
|
||||
),
|
||||
path(
|
||||
"event-invoice/<eid>",
|
||||
EventInvoiceView.as_view(),
|
||||
views.EventInvoiceView.as_view(),
|
||||
name="event-invoice",
|
||||
),
|
||||
path(
|
||||
"event-attendees",
|
||||
EventAttendeeListView.as_view(),
|
||||
views.EventAttendeeListView.as_view(),
|
||||
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.auth.decorators import permission_required
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.db.models import Subquery
|
||||
from django.db.models import OuterRef, Q, Subquery
|
||||
from django.db.models.functions import TruncMonth, TruncYear
|
||||
from django.shortcuts import render
|
||||
from django.template.defaultfilters import floatformat
|
||||
@ -19,6 +19,7 @@ import django_filters
|
||||
import django_tables2 as tables
|
||||
from dal import autocomplete
|
||||
from django_filters.views import BaseFilterView
|
||||
from django_mysql.models import GroupConcat
|
||||
from django_tables2 import A, SingleTableMixin
|
||||
from django_tables2.export.views import ExportMixin
|
||||
|
||||
@ -365,3 +366,53 @@ class EventAttendeeListView(
|
||||
|
||||
def get_table_data(self):
|
||||
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,6 +29,16 @@ class PaperworkDashboardFragment(dashboard.LinksCardDashboardFragment):
|
||||
reverse("paperwork:access-verification-report"),
|
||||
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)
|
||||
|
@ -0,0 +1,12 @@
|
||||
{% 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 %}
|
12
paperwork/templates/paperwork/certifiers_report.dj.html
Normal file
12
paperwork/templates/paperwork/certifiers_report.dj.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% 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 +1,130 @@
|
||||
# Create your tests here.
|
||||
from django.contrib.auth import get_user_model
|
||||
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,4 +40,14 @@ urlpatterns = [
|
||||
views.AccessVerificationReport.as_view(),
|
||||
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.mixins import PermissionRequiredMixin
|
||||
from django.db import models
|
||||
from django.db.models import Case, Q, Value, When
|
||||
from django.db.models import Case, Count, Max, 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
|
||||
@ -324,3 +324,72 @@ class AccessVerificationReport(
|
||||
)
|
||||
)
|
||||
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"]
|
||||
strategy = ["cross_platform"]
|
||||
lock_version = "4.4.1"
|
||||
content_hash = "sha256:88502778249494bd3f6fd407d51671f4e465065ec58427b60993d91d0808bdfd"
|
||||
content_hash = "sha256:379d72c4be2ef3f09d6a3f9cf0517f784fa18df7367386dfc09cd40fa521a25f"
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
@ -908,6 +908,35 @@ files = [
|
||||
{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]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
@ -1681,6 +1710,15 @@ files = [
|
||||
{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]]
|
||||
name = "soupsieve"
|
||||
version = "2.3.2.post1"
|
||||
@ -1741,6 +1779,16 @@ files = [
|
||||
{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]]
|
||||
name = "tinycss2"
|
||||
version = "1.1.1"
|
||||
|
@ -125,6 +125,8 @@ debug = [
|
||||
dev = [
|
||||
"django-extensions~=3.2",
|
||||
"ipython~=8.21",
|
||||
"hypothesis[django]~=6.98",
|
||||
"tblib~=3.0",
|
||||
]
|
||||
|
||||
[tool.pdm.scripts]
|
||||
|
Loading…
Reference in New Issue
Block a user