Compare commits
No commits in common. "175c3b2c5a07b66a319f8bcc519c1aede5eeac48" and "10cfc151e1aeff0e49faca9606dee50708329378" have entirely different histories.
175c3b2c5a
...
10cfc151e1
@ -13,6 +13,8 @@ jobs:
|
||||
image: mariadb:latest
|
||||
env:
|
||||
MARIADB_ROOT_PASSWORD: whatever
|
||||
ports:
|
||||
- 3306:3306
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized"]
|
||||
steps:
|
||||
@ -30,4 +32,4 @@ jobs:
|
||||
- name: Install python dependencies
|
||||
run: pdm sync -d
|
||||
- name: Run tests
|
||||
run: pdm run -v ./manage.py test
|
||||
run: pdm run -v ./manage.py test --parallel auto
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,3 @@ __pycache__/
|
||||
*.sqlite3
|
||||
/__pypackages__/
|
||||
/markdownx/
|
||||
/media/
|
||||
|
@ -42,8 +42,6 @@ INSTALLED_APPS = [
|
||||
"django_filters",
|
||||
"django_db_views",
|
||||
"django_mysql",
|
||||
"django_sendfile",
|
||||
"django_bootstrap5",
|
||||
"tasks.apps.TasksConfig",
|
||||
"rentals.apps.RentalsConfig",
|
||||
"membershipworks.apps.MembershipworksConfig",
|
||||
@ -96,7 +94,6 @@ TIME_ZONE = "America/New_York"
|
||||
USE_I18N = False
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
USE_DEPRECATED_PYTZ = False
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
@ -121,10 +118,6 @@ LOGGING = {
|
||||
},
|
||||
}
|
||||
|
||||
MEDIA_ROOT = "media"
|
||||
MEDIA_URL = "media/"
|
||||
SENDFILE_ROOT = str(Path(__file__).parents[2] / "media" / "protected")
|
||||
|
||||
WIKI_URL = "https://wiki.claremontmakerspace.org"
|
||||
|
||||
# Django Rest Framework
|
||||
|
@ -21,5 +21,3 @@ MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa:
|
||||
|
||||
configure_hypothesis_profiles()
|
||||
settings.load_profile(os.getenv("HYPOTHESIS_PROFILE", "dev"))
|
||||
|
||||
SENDFILE_BACKEND = "django_sendfile.backends.development"
|
||||
|
@ -48,6 +48,3 @@ AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||
)
|
||||
AUTH_LDAP_GROUP_TYPE = PosixGroupType()
|
||||
AUTH_LDAP_MIRROR_GROUPS = True
|
||||
|
||||
SENDFILE_BACKEND = "django_sendfile.backends.nginx"
|
||||
SENDFILE_URL = "/media/protected"
|
||||
|
@ -15,7 +15,6 @@ Including another URLconf
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
from django.urls import include, path
|
||||
@ -64,4 +63,3 @@ urlpatterns = [
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns.append(path("__debug__/", include("debug_toolbar.urls")))
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
@ -1,63 +0,0 @@
|
||||
import calendar
|
||||
|
||||
import django_tables2 as tables
|
||||
|
||||
from .models import HIDEvent
|
||||
|
||||
|
||||
class UnitTimeTable(tables.Table):
|
||||
members = tables.columns.Column()
|
||||
members_delta = tables.columns.TemplateColumn(
|
||||
"{{ value|floatformat:2}}%", verbose_name="Δ Members"
|
||||
)
|
||||
access_count = tables.columns.Column()
|
||||
access_count_delta = tables.columns.TemplateColumn(
|
||||
"{{ value|floatformat:2}}%", verbose_name="Δ Access Count"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ("members", "members_delta", "access_count", "access_count_delta")
|
||||
|
||||
|
||||
class DeniedAccessTable(tables.Table):
|
||||
name = tables.TemplateColumn(
|
||||
"{{ record.forename|default:'' }} {{ record.surname|default:'' }}"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = HIDEvent
|
||||
|
||||
fields = (
|
||||
"timestamp",
|
||||
"door",
|
||||
"event_type",
|
||||
"name",
|
||||
"raw_card_number",
|
||||
"decoded_card_number",
|
||||
)
|
||||
|
||||
|
||||
class MostActiveMembersTable(tables.Table):
|
||||
name = tables.Column()
|
||||
access_count = tables.Column()
|
||||
|
||||
|
||||
class DetailByDayTable(tables.Table):
|
||||
timestamp__date = tables.DateColumn(verbose_name="Date")
|
||||
name = tables.Column()
|
||||
access_count = tables.Column()
|
||||
|
||||
|
||||
class BusiestDayOfWeekTable(tables.Table):
|
||||
timestamp__week_day = tables.Column("Week Day")
|
||||
events = tables.Column()
|
||||
members = tables.Column()
|
||||
|
||||
def render_timestamp__week_day(self, value):
|
||||
return calendar.day_name[(value - 2) % 7]
|
||||
|
||||
|
||||
class BusiestTimeOfDayTable(tables.Table):
|
||||
timestamp__hour = tables.TemplateColumn("{{ value }}:00", verbose_name="Hour")
|
||||
events = tables.Column()
|
||||
members = tables.Column()
|
@ -1,3 +1,4 @@
|
||||
import calendar
|
||||
import datetime
|
||||
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
@ -18,14 +19,6 @@ from django_tables2 import SingleTableMixin
|
||||
from django_tables2.export.views import ExportMixin
|
||||
|
||||
from .models import Door, HIDEvent
|
||||
from .tables import (
|
||||
BusiestDayOfWeekTable,
|
||||
BusiestTimeOfDayTable,
|
||||
DeniedAccessTable,
|
||||
DetailByDayTable,
|
||||
MostActiveMembersTable,
|
||||
UnitTimeTable,
|
||||
)
|
||||
|
||||
REPORTS = []
|
||||
|
||||
@ -103,6 +96,20 @@ class BaseAccessReport(
|
||||
return context
|
||||
|
||||
|
||||
class UnitTimeTable(tables.Table):
|
||||
members = tables.columns.Column()
|
||||
members_delta = tables.columns.TemplateColumn(
|
||||
"{{ value|floatformat:2}}%", verbose_name="Δ Members"
|
||||
)
|
||||
access_count = tables.columns.Column()
|
||||
access_count_delta = tables.columns.TemplateColumn(
|
||||
"{{ value|floatformat:2}}%", verbose_name="Δ Access Count"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ("members", "members_delta", "access_count", "access_count_delta")
|
||||
|
||||
|
||||
@register_report
|
||||
class AccessPerUnitTime(BaseAccessReport):
|
||||
table_class = UnitTimeTable
|
||||
@ -202,6 +209,24 @@ class AccessPerUnitTime(BaseAccessReport):
|
||||
)
|
||||
|
||||
|
||||
class DeniedAccessTable(tables.Table):
|
||||
name = tables.TemplateColumn(
|
||||
"{{ record.forename|default:'' }} {{ record.surname|default:'' }}"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = HIDEvent
|
||||
|
||||
fields = (
|
||||
"timestamp",
|
||||
"door",
|
||||
"event_type",
|
||||
"name",
|
||||
"raw_card_number",
|
||||
"decoded_card_number",
|
||||
)
|
||||
|
||||
|
||||
@register_report
|
||||
class DeniedAccess(BaseAccessReport):
|
||||
_report_name = "Denied Access"
|
||||
@ -219,6 +244,11 @@ class DeniedAccess(BaseAccessReport):
|
||||
)
|
||||
|
||||
|
||||
class MostActiveMembersTable(tables.Table):
|
||||
name = tables.Column()
|
||||
access_count = tables.Column()
|
||||
|
||||
|
||||
@register_report
|
||||
class MostActiveMembers(BaseAccessReport):
|
||||
_report_name = "Most Active Members"
|
||||
@ -241,6 +271,12 @@ class MostActiveMembers(BaseAccessReport):
|
||||
)
|
||||
|
||||
|
||||
class DetailByDayTable(tables.Table):
|
||||
timestamp__date = tables.DateColumn(verbose_name="Date")
|
||||
name = tables.Column()
|
||||
access_count = tables.Column()
|
||||
|
||||
|
||||
@register_report
|
||||
class DetailByDay(BaseAccessReport):
|
||||
_report_name = "Detail by Day"
|
||||
@ -263,6 +299,15 @@ class DetailByDay(BaseAccessReport):
|
||||
)
|
||||
|
||||
|
||||
class BusiestDayOfWeekTable(tables.Table):
|
||||
timestamp__week_day = tables.Column("Week Day")
|
||||
events = tables.Column()
|
||||
members = tables.Column()
|
||||
|
||||
def render_timestamp__week_day(self, value):
|
||||
return calendar.day_name[(value - 2) % 7]
|
||||
|
||||
|
||||
@register_report
|
||||
class BusiestDayOfWeek(BaseAccessReport):
|
||||
_report_name = "Busiest Day of the Week"
|
||||
@ -281,6 +326,12 @@ class BusiestDayOfWeek(BaseAccessReport):
|
||||
)
|
||||
|
||||
|
||||
class BusiestTimeOfDayTable(tables.Table):
|
||||
timestamp__hour = tables.TemplateColumn("{{ value }}:00", verbose_name="Hour")
|
||||
events = tables.Column()
|
||||
members = tables.Column()
|
||||
|
||||
|
||||
@register_report
|
||||
class BusiestTimeOfDay(BaseAccessReport):
|
||||
_report_name = "Busiest Time of Day"
|
||||
|
@ -14,7 +14,6 @@ from .models import (
|
||||
Event,
|
||||
EventExt,
|
||||
EventInstructor,
|
||||
EventInvoice,
|
||||
EventMeetingTime,
|
||||
Flag,
|
||||
Member,
|
||||
@ -120,15 +119,11 @@ class EventInstructorAdmin(admin.ModelAdmin):
|
||||
search_fields = ["name", "member__account_name"]
|
||||
|
||||
|
||||
class EventInvoiceInline(admin.StackedInline):
|
||||
model = EventInvoice
|
||||
|
||||
|
||||
@admin.register(EventExt)
|
||||
class EventAdmin(DjangoObjectActions, admin.ModelAdmin):
|
||||
inlines = [EventInvoiceInline, EventMeetingTimeInline]
|
||||
inlines = [EventMeetingTimeInline]
|
||||
list_display = [
|
||||
"unescaped_title",
|
||||
"title",
|
||||
"start",
|
||||
"duration",
|
||||
"count",
|
||||
@ -163,10 +158,6 @@ class EventAdmin(DjangoObjectActions, admin.ModelAdmin):
|
||||
fields.append("_details_timestamp")
|
||||
return fields
|
||||
|
||||
@admin.display(ordering="title")
|
||||
def unescaped_title(self, obj):
|
||||
return obj.unescaped_title
|
||||
|
||||
@admin.display(ordering="duration")
|
||||
def duration(self, obj):
|
||||
return obj.duration
|
||||
|
@ -2,48 +2,32 @@ from django.urls import reverse
|
||||
|
||||
import dashboard
|
||||
from dashboard import Link
|
||||
from membershipworks.models import EventExt, Member
|
||||
|
||||
|
||||
@dashboard.register
|
||||
class MembershipworksDashboardFragment(dashboard.LinksCardDashboardFragment):
|
||||
name = "MembershipWorks"
|
||||
|
||||
@property
|
||||
def links(self):
|
||||
links = [
|
||||
Link(
|
||||
"Upcoming Events",
|
||||
reverse("membershipworks:upcoming-events"),
|
||||
permission="membershipworks.view_event",
|
||||
tooltip="Generator for Wordpress posts",
|
||||
),
|
||||
Link(
|
||||
"Event Report",
|
||||
reverse("membershipworks:event-index-report"),
|
||||
permission="membershipworks.view_event",
|
||||
),
|
||||
Link(
|
||||
"Event Attendees",
|
||||
reverse("membershipworks:event-attendees"),
|
||||
permission="membershipworks.view_event",
|
||||
),
|
||||
Link(
|
||||
"Missing Paperwork",
|
||||
reverse("membershipworks:missing-paperwork-report"),
|
||||
permission="membershipworks.view_member",
|
||||
),
|
||||
]
|
||||
|
||||
member = Member.from_user(self.request.user)
|
||||
if (
|
||||
member is not None
|
||||
and EventExt.objects.filter(instructor__member=member).exists()
|
||||
):
|
||||
links.append(
|
||||
Link(
|
||||
"My Events", reverse("membershipworks:user-events"), permission=None
|
||||
)
|
||||
)
|
||||
|
||||
return links
|
||||
links = [
|
||||
Link(
|
||||
"Upcoming Events",
|
||||
reverse("membershipworks:upcoming-events"),
|
||||
permission="membershipworks.view_event",
|
||||
tooltip="Generator for Wordpress posts",
|
||||
),
|
||||
Link(
|
||||
"Event Report",
|
||||
reverse("membershipworks:event-index-report"),
|
||||
permission="membershipworks.view_event",
|
||||
),
|
||||
Link(
|
||||
"Event Attendees",
|
||||
reverse("membershipworks:event-attendees"),
|
||||
permission="membershipworks.view_event",
|
||||
),
|
||||
Link(
|
||||
"Missing Paperwork",
|
||||
reverse("membershipworks:missing-paperwork-report"),
|
||||
permission="membershipworks.view_member",
|
||||
),
|
||||
]
|
||||
|
@ -1,38 +0,0 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
|
||||
from membershipworks.models import EventExt
|
||||
|
||||
|
||||
class EventInvoiceForm(forms.Form):
|
||||
reviewed_invoice = forms.BooleanField(
|
||||
label="I have reviewed this invoice and confirm that the course information, materials fee, course fees, enrollment information, invoice amount and all other details are correct.",
|
||||
help_text='Please contact us at <a href="mailto:info@claremontmakerspace.org">info@claremontmakerspace.org</a> if corrections are required.',
|
||||
required=True,
|
||||
)
|
||||
verified_payment_and_contact = forms.BooleanField(
|
||||
label="I have verified my contact and payment information.",
|
||||
help_text='You can <a href="https://claremontmakerspace.org/membersonly/">change your payment information here</a>. It may take up to an hour for your changes to take effect.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
def __init__(self, *args, event: EventExt, user: AbstractBaseUser, **kwargs):
|
||||
self.event = event
|
||||
self.user = user
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
if self.event.total_due_to_instructor is None:
|
||||
raise forms.ValidationError(
|
||||
"Event missing required information to generate invoice"
|
||||
)
|
||||
|
||||
if not self.event.user_is_instructor(self.user):
|
||||
raise forms.ValidationError(
|
||||
"You are not the listed as the instructor for this event"
|
||||
)
|
||||
|
||||
if hasattr(self.event, "eventinvoice"):
|
||||
raise forms.ValidationError("Invoice already exists for this event")
|
||||
|
||||
return super().clean()
|
@ -1,65 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||
from django.template import loader
|
||||
|
||||
import mdformat
|
||||
from markdownify import markdownify
|
||||
|
||||
from membershipworks.models import EventInvoice
|
||||
|
||||
|
||||
def make_multipart_email(
|
||||
subject: str, html_body: str, to: tuple[str]
|
||||
) -> EmailMultiAlternatives:
|
||||
plain_body = mdformat.text(markdownify(html_body), extensions={"tables"})
|
||||
|
||||
email = EmailMultiAlternatives(
|
||||
subject,
|
||||
plain_body,
|
||||
from_email="CMS Invoices <invoices@claremontmakerspace.org>",
|
||||
to=to,
|
||||
reply_to=["Claremont MakerSpace <Info@ClaremontMakerSpace.org>"],
|
||||
)
|
||||
|
||||
email.attach_alternative(html_body, "text/html")
|
||||
return email
|
||||
|
||||
|
||||
def make_instructor_email(
|
||||
invoice: EventInvoice, pdf: bytes, event_url: str
|
||||
) -> EmailMessage:
|
||||
template = loader.get_template(
|
||||
"membershipworks/email/event_invoice_instructor.dj.html"
|
||||
)
|
||||
html_body = template.render({"invoice": invoice, "event_url": event_url})
|
||||
|
||||
message = make_multipart_email(
|
||||
f'Your CMS instructor invoice has been received for event "{invoice.event}" {invoice.event.start} - {invoice.event.end}',
|
||||
html_body,
|
||||
(invoice.event.instructor.member.sanitized_mailbox(),),
|
||||
)
|
||||
message.attach(f"CMS_event_invoice_{invoice.uuid}.pdf", pdf, "application/pdf")
|
||||
return message
|
||||
|
||||
|
||||
def make_admin_email(invoice: EventInvoice, pdf: bytes, event_url: str) -> EmailMessage:
|
||||
template = loader.get_template("membershipworks/email/event_invoice_admin.dj.html")
|
||||
html_body = template.render({"invoice": invoice, "event_url": event_url})
|
||||
|
||||
message = make_multipart_email(
|
||||
f'CMS instructor invoice created for event "{invoice.event}" {invoice.event.start} - {invoice.event.end}',
|
||||
html_body,
|
||||
# TODO: should this be in database instead?
|
||||
settings.INVOICE_HANDLERS,
|
||||
)
|
||||
message.attach(f"CMS_event_invoice_{invoice.uuid}.pdf", pdf, "application/pdf")
|
||||
return message
|
||||
|
||||
|
||||
def make_invoice_emails(
|
||||
invoice: EventInvoice, pdf: bytes, event_url: str
|
||||
) -> list[EmailMessage]:
|
||||
return [
|
||||
make_instructor_email(invoice, pdf, event_url),
|
||||
make_admin_email(invoice, pdf, event_url),
|
||||
]
|
@ -1,41 +0,0 @@
|
||||
# Generated by Django 5.0.2 on 2024-03-08 21:30
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0015_eventmeetingtime_end_after_start"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EventInvoice",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("date_submitted", models.DateField()),
|
||||
("date_paid", models.DateField(blank=True, null=True)),
|
||||
("pdf", models.FileField(upload_to="invoices/%Y/%m/%d/")),
|
||||
("amount", models.DecimalField(decimal_places=4, max_digits=13)),
|
||||
(
|
||||
"event",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="invoice",
|
||||
to="membershipworks.eventext",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,10 +1,8 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import django.core.mail.message
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.db import models
|
||||
from django.db.models import (
|
||||
Case,
|
||||
@ -17,14 +15,11 @@ from django.db.models import (
|
||||
Q,
|
||||
Subquery,
|
||||
Sum,
|
||||
Value,
|
||||
When,
|
||||
)
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
import nh3
|
||||
from django_db_views.db_view import DBView
|
||||
|
||||
|
||||
@ -429,12 +424,8 @@ class Event(BaseModel):
|
||||
|
||||
_allowed_missing_fields = ["cap", "edp", "adn"]
|
||||
|
||||
@property
|
||||
def unescaped_title(self):
|
||||
return nh3.clean(self.title, tags=set())
|
||||
|
||||
def __str__(self):
|
||||
return self.unescaped_title
|
||||
return self.title
|
||||
|
||||
|
||||
class EventInstructor(models.Model):
|
||||
@ -480,7 +471,7 @@ class EventExtQuerySet(models.QuerySet["EventExt"]):
|
||||
"amount",
|
||||
"materials",
|
||||
"amount_without_materials",
|
||||
"instructor_revenue",
|
||||
"instructor_fee",
|
||||
"instructor_amount",
|
||||
]
|
||||
},
|
||||
@ -552,15 +543,6 @@ class EventExt(Event):
|
||||
)
|
||||
details = models.JSONField(null=True, blank=True)
|
||||
|
||||
def get_absolute_url(self) -> str:
|
||||
return reverse("membershipworks:event-detail", kwargs={"eid": self.eid})
|
||||
|
||||
def user_is_instructor(self, user: AbstractBaseUser) -> bool:
|
||||
member = Member.from_user(user)
|
||||
if member is not None:
|
||||
return self.instructor.member == member
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
verbose_name = "event"
|
||||
ordering = ["-start"]
|
||||
@ -588,42 +570,6 @@ class EventMeetingTime(models.Model):
|
||||
]
|
||||
|
||||
|
||||
class EventInvoice(models.Model):
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
event = models.OneToOneField(
|
||||
EventExt, on_delete=models.PROTECT, related_name="invoice"
|
||||
)
|
||||
date_submitted = models.DateField()
|
||||
date_paid = models.DateField(blank=True, null=True)
|
||||
pdf = models.FileField(upload_to="protected/invoices/%Y/%m/%d/")
|
||||
amount = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'"{self.event}" submitted={self.date_submitted} paid:{self.date_paid} ${self.amount}'
|
||||
|
||||
|
||||
class EventTicketTypeQuerySet(models.QuerySet["EventTicketType"]):
|
||||
def group_by_ticket_type(self):
|
||||
return self.values("is_members_ticket").annotate(
|
||||
label=Case(
|
||||
When(Q(is_members_ticket=True), Value("Members")),
|
||||
default=Value("Non-Members"),
|
||||
),
|
||||
actual_price=F("actual_price"),
|
||||
**{
|
||||
field: Sum(field)
|
||||
for field in [
|
||||
"quantity",
|
||||
"materials",
|
||||
"amount",
|
||||
"amount_without_materials",
|
||||
"instructor_revenue",
|
||||
"instructor_amount",
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
||||
def get_queryset(self) -> models.QuerySet["EventTicketType"]:
|
||||
members_folder = Subquery(
|
||||
@ -669,19 +615,18 @@ class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
||||
amount_without_materials=ExpressionWrapper(
|
||||
F("amount") - F("materials"), output_field=models.DecimalField()
|
||||
),
|
||||
instructor_revenue=ExpressionWrapper(
|
||||
instructor_fee=ExpressionWrapper(
|
||||
F("amount_without_materials") * F("event__instructor_percentage"),
|
||||
output_field=models.DecimalField(),
|
||||
),
|
||||
instructor_amount=ExpressionWrapper(
|
||||
F("instructor_revenue") + F("materials"),
|
||||
output_field=models.DecimalField(),
|
||||
F("instructor_fee") + F("materials"), output_field=models.DecimalField()
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class EventTicketType(DBView):
|
||||
objects = EventTicketTypeManager.from_queryset(EventTicketTypeQuerySet)()
|
||||
objects = EventTicketTypeManager()
|
||||
|
||||
event = models.ForeignKey(
|
||||
EventExt, on_delete=models.CASCADE, related_name="ticket_types"
|
||||
|
@ -1,82 +0,0 @@
|
||||
@page {
|
||||
size: letter portrait;
|
||||
margin: 1.2in 0.7in;
|
||||
|
||||
@top-center {
|
||||
content: "Event Invoice";
|
||||
font-size: 1.2em;
|
||||
color: #444;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
@top-left {
|
||||
vertical-align: bottom;
|
||||
content: element(header);
|
||||
}
|
||||
|
||||
@top-right {
|
||||
content: "";
|
||||
vertical-align: bottom;
|
||||
background-position: bottom;
|
||||
background-image: url("https://claremontmakerspace.org/wp-content/uploads/2018/06/cms_logo.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
@bottom-left {
|
||||
content: element(footer-left);
|
||||
}
|
||||
|
||||
@bottom-right {
|
||||
content: element(footer-right);
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
footer .left {
|
||||
position: running(footer-left);
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
footer .right {
|
||||
position: running(footer-right);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
header {
|
||||
position: running(header);
|
||||
}
|
||||
|
||||
/* TODO: probably should just fix this server side */
|
||||
:root {
|
||||
--bs-font-sans-serif: roboto;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
/* Bootstrap fixes for Weasyprint */
|
||||
.table-group-divider {
|
||||
border-top: 2px solid currentcolor;
|
||||
}
|
||||
|
||||
.d-md-block {
|
||||
display: block !important;
|
||||
}
|
||||
.col-md-7 {
|
||||
flex: 0 0 auto;
|
||||
width: 58.33333333%;
|
||||
}
|
||||
.col-md-4 {
|
||||
flex: 0 0 auto;
|
||||
width: 33.33333333%;
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.template.defaultfilters import floatformat
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import SafeString
|
||||
|
||||
import django_tables2 as tables
|
||||
|
||||
from .models import EventAttendee, EventExt, Member
|
||||
|
||||
|
||||
class DurationColumn(tables.Column):
|
||||
def render(self, value: timedelta):
|
||||
if value is None:
|
||||
return None
|
||||
return floatformat(value.total_seconds() / 60 / 60, -2)
|
||||
|
||||
def value(self, value: timedelta):
|
||||
if value is None:
|
||||
return None
|
||||
return value.total_seconds() / 60 / 60
|
||||
|
||||
|
||||
class EventTable(tables.Table):
|
||||
title = tables.TemplateColumn(
|
||||
template_code=(
|
||||
'<a title="MembershipWorks" href="https://membershipworks.com/admin/#!event/admin/{{ record.url }}">{{ value }}</a> '
|
||||
'<a title="Admin" href="{% url "admin:membershipworks_eventext_change" record.pk %}"><i class="bi bi-pencil-square"></i></a> '
|
||||
'<a title="Details" href="{% url "membershipworks:event-detail" record.pk %}"><i class="bi bi-receipt"></i></a> '
|
||||
),
|
||||
accessor="unescaped_title",
|
||||
)
|
||||
occurred = tables.BooleanColumn(visible=False)
|
||||
start = tables.DateColumn("N d, Y")
|
||||
duration = DurationColumn()
|
||||
person_hours = DurationColumn()
|
||||
meetings = tables.Column()
|
||||
gross_revenue = tables.Column()
|
||||
total_due_to_instructor = tables.Column()
|
||||
net_revenue = tables.Column()
|
||||
invoice__date_submitted = tables.DateColumn(verbose_name="Invoice Submitted")
|
||||
invoice__date_paid = tables.DateColumn(verbose_name="Invoice Paid")
|
||||
|
||||
class Meta:
|
||||
model = EventExt
|
||||
fields = (
|
||||
"title",
|
||||
"occurred",
|
||||
"start",
|
||||
"instructor",
|
||||
"category",
|
||||
"count",
|
||||
"cap",
|
||||
"meetings",
|
||||
"duration",
|
||||
"person_hours",
|
||||
"gross_revenue",
|
||||
"total_due_to_instructor",
|
||||
"net_revenue",
|
||||
)
|
||||
row_attrs = {
|
||||
"class": lambda record: (
|
||||
"" if record.occurred else "text-decoration-line-through table-danger"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class EventSummaryTable(tables.Table):
|
||||
event_count = tables.Column("Events")
|
||||
canceled_event_count = tables.Column("Canceled Events")
|
||||
count__sum = tables.Column("Tickets")
|
||||
instructor__count = tables.Column("Unique Instructors")
|
||||
meetings__sum = tables.Column("Meetings")
|
||||
duration__sum = DurationColumn("Class Hours")
|
||||
person_hours__sum = DurationColumn("Person Hours")
|
||||
gross_revenue__sum = tables.Column("Gross Revenue")
|
||||
total_due_to_instructor__sum = tables.Column("Total Due to Instructor")
|
||||
net_revenue__sum = tables.Column("Net Revenue")
|
||||
|
||||
|
||||
class UserEventTable(EventTable):
|
||||
title = tables.Column(linkify=True, accessor="unescaped_title")
|
||||
instructor = None
|
||||
person_hours = None
|
||||
gross_revenue = None
|
||||
net_revenue = None
|
||||
|
||||
|
||||
class InvoiceMoneyColumn(tables.columns.Column):
|
||||
def render(self, value):
|
||||
return f"${super().render(value):.2f}"
|
||||
|
||||
|
||||
class InvoiceMoneyFooterColumn(InvoiceMoneyColumn):
|
||||
def render_footer(self, bound_column, table):
|
||||
value = getattr(table.event, bound_column.accessor)
|
||||
if value is not None:
|
||||
return f"${value:.2f}"
|
||||
else:
|
||||
return bound_column.default
|
||||
|
||||
|
||||
class InvoiceTable(tables.Table):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop("event")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _math_header(name: str, formula: str) -> SafeString:
|
||||
return format_html(
|
||||
'{} <div class="text-nowrap font-monospace fw-light">[{}]</div>',
|
||||
name,
|
||||
formula,
|
||||
)
|
||||
|
||||
label = tables.Column("Ticket Type", footer="Subtotals")
|
||||
list_price = InvoiceMoneyColumn("Ticket Price")
|
||||
actual_price = InvoiceMoneyColumn(_math_header("Actual Price", "P"))
|
||||
quantity = tables.Column(
|
||||
_math_header("Quantity", "Q"),
|
||||
footer=lambda table: table.event.quantity,
|
||||
)
|
||||
amount = InvoiceMoneyFooterColumn(_math_header("Amount", "A=P*Q"))
|
||||
materials = InvoiceMoneyFooterColumn(
|
||||
_math_header("CMS Collected Materials Fee", "M=m*Q")
|
||||
)
|
||||
amount_without_materials = InvoiceMoneyFooterColumn(
|
||||
_math_header("Event Revenue Base", "B=A-M")
|
||||
)
|
||||
instructor_revenue = InvoiceMoneyFooterColumn(
|
||||
_math_header("Instructor Percentage Revenue", "R=B*I")
|
||||
)
|
||||
instructor_amount = InvoiceMoneyFooterColumn(
|
||||
_math_header("Amount Due to Instructor", "R+M")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
"class": "table table-sm mx-auto w-auto",
|
||||
"tbody": {"class": "table-group-divider"},
|
||||
"tfoot": {"class": "table-group-divider"},
|
||||
}
|
||||
orderable = False
|
||||
|
||||
|
||||
class EventAttendeeTable(tables.Table):
|
||||
class Meta:
|
||||
model = EventAttendee
|
||||
fields = ("name", "email")
|
||||
|
||||
|
||||
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",
|
||||
]
|
@ -1,6 +0,0 @@
|
||||
<p>
|
||||
New invoice received from
|
||||
<b>{{ invoice.event.instructor }}</b> on <b>{{ invoice.date_submitted }}</b>.
|
||||
</p>
|
||||
|
||||
{% include "membershipworks/email/event_invoice_details_fragment.dj.html" %}
|
@ -1,19 +0,0 @@
|
||||
<p>
|
||||
<div>
|
||||
<b>Invoice #:</b> {{ invoice.uuid }}
|
||||
</div>
|
||||
<div>
|
||||
<b>Event id:</b> {{ invoice.event.eid }}
|
||||
</div>
|
||||
<div>
|
||||
<b>Event title:</b> {{ invoice.event }}
|
||||
</div>
|
||||
<div>
|
||||
<b>Event duration:</b> {{ invoice.event.start }} - {{ invoice.event.end }}
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ event_url }}">
|
||||
<b>View this event and invoice in CMSManage</b>
|
||||
</a>
|
||||
</div>
|
||||
</p>
|
@ -1,5 +0,0 @@
|
||||
<p>
|
||||
Your invoice for <b>{{ invoice.event }}</b> has been received. A copy is attached for your records.
|
||||
</p>
|
||||
|
||||
{% include "membershipworks/email/event_invoice_details_fragment.dj.html" %}
|
@ -1,46 +0,0 @@
|
||||
{% extends "base.dj.html" %}
|
||||
|
||||
{% load nh3_tags %}
|
||||
{% load django_bootstrap5 %}
|
||||
|
||||
{% block title %}Event Invoice for {{ event.details.ttl|nh3 }}{% endblock %}
|
||||
{% block admin_link %}
|
||||
{% url 'admin:membershipworks_eventext_change' event.pk %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
{% include "membershipworks/event_invoice.dj.html" %}
|
||||
|
||||
<div class="card w-auto mt-5">
|
||||
<div class="card-body">
|
||||
{% if event.invoice %}
|
||||
<div class="card-text text-center">
|
||||
<p>
|
||||
Invoice submitted on {{ event.invoice.date_submitted }}
|
||||
for ${{ event.invoice.amount|floatformat:2 }},
|
||||
{% if event.invoice.date_paid %}
|
||||
paid on {{ event.invoice.date_paid }}
|
||||
{% else %}
|
||||
not paid yet
|
||||
{% endif %}
|
||||
</p>
|
||||
<a class="btn btn-primary"
|
||||
href="{% url 'membershipworks:event-invoice-pdf' event.invoice.pk %}">View PDF</a>
|
||||
</div>
|
||||
{% elif event.total_due_to_instructor is None %}
|
||||
<p class="card-text text-center">
|
||||
This event is missing required information to generate an invoice. Please contact us at <a href="mailto:info@claremontmakerspace.org">info@claremontmakerspace.org</a>.
|
||||
</p>
|
||||
{% elif user_is_instructor %}
|
||||
<form method="post" class="card-text">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<div class="text-center">{% bootstrap_button button_type="submit" content="Submit Invoice" %}</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>No invoice has been created for this event, and you are not listed as the the instructor.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,65 +1,52 @@
|
||||
{% extends "base.dj.html" %}
|
||||
|
||||
{% load nh3_tags %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-7">
|
||||
<p>
|
||||
<b>Event Name:</b> {{ event.details.ttl|nh3 }}
|
||||
</p>
|
||||
<p>
|
||||
<b>Event ID:</b> {{ event.eid }}
|
||||
</p>
|
||||
<p>
|
||||
<b>Instructor:</b> {{ event.instructor }}
|
||||
</p>
|
||||
<p>
|
||||
{% with meeting_times=event.meeting_times.all %}
|
||||
{% if meeting_times|length == 0 %}
|
||||
<b>Dates of Event:</b> Not set
|
||||
{% elif meeting_times|length == 1 %}
|
||||
<b>Date of Event:</b> {{ meeting_times.0.start }} – {{ meeting_times.0.end }}
|
||||
{% else %}
|
||||
<b>Dates of Event:</b>
|
||||
<ul>
|
||||
{% for meeting_time in meeting_times %}<li>{{ meeting_time.start }} – {{ meeting_time.end }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</p>
|
||||
<p>
|
||||
<b>Attendees:</b> {{ event.details.cnt }}/{{ event.details.cap }}
|
||||
</p>
|
||||
<p>
|
||||
<b>Materials Fee <span class="font-monospace fw-light">[m]</span>:</b>
|
||||
{% if event.materials_fee_included_in_price is None %}
|
||||
Unknown if included in price
|
||||
{% elif event.materials_fee_included_in_price %}
|
||||
{% if event.materials_fee is not None %}
|
||||
${{ event.materials_fee|floatformat:2 }}
|
||||
{% else %}
|
||||
Not defined
|
||||
{% endif %}
|
||||
{% block title %}Event Invoice for {{ event.details.ttl|nh3 }}{% endblock %}
|
||||
{% block admin_link %}
|
||||
{% url 'admin:membershipworks_eventext_change' event.pk %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<p>
|
||||
<b>Event Name:</b> {{ event.details.ttl|nh3 }}
|
||||
</p>
|
||||
<p>
|
||||
<b>Instructor:</b> {{ event.instructor }}
|
||||
</p>
|
||||
<p>
|
||||
{% with meeting_times=event.meeting_times.all %}
|
||||
{% if meeting_times|length == 0 %}
|
||||
<b>Dates of Event:</b> Not set
|
||||
{% elif meeting_times|length == 1 %}
|
||||
<b>Date of Event:</b> {{ meeting_times.0.start }} - {{ meeting_times.0.end }}
|
||||
{% else %}
|
||||
Not collected by CMS
|
||||
<b>Dates of Event:</b>
|
||||
<ul>
|
||||
{% for meeting_time in meeting_times %}<li>{{ meeting_time.start }} - {{ meeting_time.end }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<b>Instructor Percentage <span class="font-monospace fw-light">[I]</span>:</b> {{ event.instructor_percentage }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="vr d-none d-md-block m-4 p-0"></div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div>
|
||||
<h3>Remit to:</h3>
|
||||
<div>{{ event.instructor.member.account_name }}</div>
|
||||
<div>{{ event.instructor.member.address_street }}</div>
|
||||
<div>
|
||||
{{ event.instructor.member.address_city }},
|
||||
{{ event.instructor.member.address_state_province }}
|
||||
{{ event.instructor.member.address_postal_code }}
|
||||
</div>
|
||||
<div>{{ event.instructor.member.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% render_table table "membershipworks/tables/invoice_table.dj.html" %}
|
||||
{% endwith %}
|
||||
</p>
|
||||
<p>
|
||||
<b>Attendees:</b> {{ event.details.cnt }}/{{ event.details.cap }}
|
||||
</p>
|
||||
<p>
|
||||
<b>Materials Fee [m]:</b>
|
||||
{% if event.materials_fee_included_in_price is None %}
|
||||
Unknown if included in price
|
||||
{% elif event.materials_fee_included_in_price %}
|
||||
{% if event.materials_fee is not None %}
|
||||
${{ event.materials_fee|floatformat:2 }}
|
||||
{% else %}
|
||||
Not defined
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Not collected by CMS
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<b>Instructor Percentage [I]:</b> {{ event.instructor_percentage }}
|
||||
</p>
|
||||
{% render_table table "membershipworks/tables/invoice_table.dj.html" %}
|
||||
{% endblock %}
|
||||
|
@ -1,29 +0,0 @@
|
||||
{% load static %}
|
||||
|
||||
{% load nh3_tags %}
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static "membershipworks/css/event_invoice_pdf.css" %}" media="print">
|
||||
<title>Event Invoice for {{ event.details.ttl|nh3 }}</title>
|
||||
</head>
|
||||
|
||||
<body style="font-size: 10pt;">
|
||||
<header>
|
||||
<div>TwinState MakerSpaces, Inc.</div>
|
||||
<div>PO Box 100</div>
|
||||
<div>Lebanon, NH 03766-0100</div>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="left">Generated for {{ user }} on {{ now|date:"Y-m-d" }} {{ now|time:"H:i T" }} by CMSManage</div>
|
||||
<div class="right">
|
||||
Invoice ID: <span class="font-monospace text-nowrap">{{ invoice_uuid }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{% include "membershipworks/event_invoice.dj.html" %}
|
||||
</body>
|
@ -1,10 +0,0 @@
|
||||
{% extends "base.dj.html" %}
|
||||
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block title %}My Events{% endblock %}
|
||||
{% block content %}
|
||||
{% include "cmsmanage/components/download_table.dj.html" %}
|
||||
|
||||
{% render_table table %}
|
||||
{% endblock %}
|
@ -31,19 +31,9 @@ urlpatterns = [
|
||||
name="event-month-report",
|
||||
),
|
||||
path(
|
||||
"my-events/",
|
||||
views.UserEventView.as_view(),
|
||||
name="user-events",
|
||||
),
|
||||
path(
|
||||
"event/<eid>",
|
||||
views.EventDetailView.as_view(),
|
||||
name="event-detail",
|
||||
),
|
||||
path(
|
||||
"event/invoice/<uuid:uuid>.pdf",
|
||||
views.EventInvoicePDFView.as_view(),
|
||||
name="event-invoice-pdf",
|
||||
"event-invoice/<eid>",
|
||||
views.EventInvoiceView.as_view(),
|
||||
name="event-invoice",
|
||||
),
|
||||
path(
|
||||
"event-attendees",
|
||||
|
@ -1,56 +1,31 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.mixins import (
|
||||
AccessMixin,
|
||||
PermissionRequiredMixin,
|
||||
)
|
||||
from django.core import mail
|
||||
from django.core.files.base import ContentFile
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.db.models import OuterRef, Q, Subquery
|
||||
from django.db.models.functions import TruncMonth, TruncYear
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.template.defaultfilters import floatformat
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic.dates import (
|
||||
ArchiveIndexView,
|
||||
MonthArchiveView,
|
||||
YearArchiveView,
|
||||
)
|
||||
from django.views.generic.detail import BaseDetailView
|
||||
from django.views.generic.edit import FormMixin, ProcessFormView
|
||||
|
||||
import django_filters
|
||||
import django_tables2 as tables
|
||||
import weasyprint
|
||||
from dal import autocomplete
|
||||
from django_filters.views import BaseFilterView
|
||||
from django_mysql.models import GroupConcat
|
||||
from django_sendfile import sendfile
|
||||
from django_tables2 import A, SingleTableMixin
|
||||
from django_tables2.export.views import ExportMixin
|
||||
from django_weasyprint.utils import django_url_fetcher
|
||||
|
||||
from membershipworks.membershipworks_api import MembershipWorks
|
||||
|
||||
from .forms import EventInvoiceForm
|
||||
from .invoice_email import make_invoice_emails
|
||||
from .models import EventAttendee, EventExt, EventInvoice, Member
|
||||
from .tables import (
|
||||
EventAttendeeTable,
|
||||
EventSummaryTable,
|
||||
EventTable,
|
||||
InvoiceTable,
|
||||
MissingPaperworkTable,
|
||||
UserEventTable,
|
||||
)
|
||||
from .models import EventAttendee, EventExt, Member
|
||||
|
||||
|
||||
class MemberAutocomplete(autocomplete.Select2QuerySetView):
|
||||
@ -143,6 +118,72 @@ def upcoming_events(request):
|
||||
return render(request, "membershipworks/upcoming_events.dj.html", context)
|
||||
|
||||
|
||||
class DurationColumn(tables.Column):
|
||||
def render(self, value: timedelta):
|
||||
if value is None:
|
||||
return None
|
||||
return floatformat(value.total_seconds() / 60 / 60, -2)
|
||||
|
||||
def value(self, value: timedelta):
|
||||
if value is None:
|
||||
return None
|
||||
return value.total_seconds() / 60 / 60
|
||||
|
||||
|
||||
class EventTable(tables.Table):
|
||||
title = tables.TemplateColumn(
|
||||
template_code=(
|
||||
'<a title="MembershipWorks" href="https://membershipworks.com/admin/#!event/admin/{{ record.url }}">{{ value }}</a> '
|
||||
'<a title="Admin" href="{% url "admin:membershipworks_eventext_change" record.pk %}"><i class="bi bi-pencil-square"></i></a> '
|
||||
'<a title="Invoice" href="{% url "membershipworks:event-invoice" record.pk %}"><i class="bi bi-receipt"></i></a> '
|
||||
),
|
||||
)
|
||||
occurred = tables.BooleanColumn(visible=False)
|
||||
start = tables.DateColumn("N d, Y")
|
||||
duration = DurationColumn()
|
||||
person_hours = DurationColumn()
|
||||
meetings = tables.Column()
|
||||
gross_revenue = tables.Column()
|
||||
total_due_to_instructor = tables.Column()
|
||||
net_revenue = tables.Column()
|
||||
|
||||
class Meta:
|
||||
model = EventExt
|
||||
fields = (
|
||||
"title",
|
||||
"occurred",
|
||||
"start",
|
||||
"instructor",
|
||||
"category",
|
||||
"count",
|
||||
"cap",
|
||||
"meetings",
|
||||
"duration",
|
||||
"person_hours",
|
||||
"gross_revenue",
|
||||
"total_due_to_instructor",
|
||||
"net_revenue",
|
||||
)
|
||||
row_attrs = {
|
||||
"class": lambda record: (
|
||||
"" if record.occurred else "text-decoration-line-through table-danger"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class EventSummaryTable(tables.Table):
|
||||
event_count = tables.Column("Events")
|
||||
canceled_event_count = tables.Column("Canceled Events")
|
||||
count__sum = tables.Column("Tickets")
|
||||
instructor__count = tables.Column("Unique Instructors")
|
||||
meetings__sum = tables.Column("Meetings")
|
||||
duration__sum = DurationColumn("Class Hours")
|
||||
person_hours__sum = DurationColumn("Person Hours")
|
||||
gross_revenue__sum = tables.Column("Gross Revenue")
|
||||
total_due_to_instructor__sum = tables.Column("Total Due to Instructor")
|
||||
net_revenue__sum = tables.Column("Net Revenue")
|
||||
|
||||
|
||||
class EventIndexReport(
|
||||
ExportMixin, SingleTableMixin, PermissionRequiredMixin, ArchiveIndexView
|
||||
):
|
||||
@ -231,7 +272,7 @@ class EventMonthReport(
|
||||
return (
|
||||
super()
|
||||
.get_table_data()
|
||||
.select_related("category", "instructor", "invoice")
|
||||
.select_related("category", "instructor")
|
||||
.with_financials()
|
||||
)
|
||||
|
||||
@ -239,163 +280,65 @@ class EventMonthReport(
|
||||
return f"mw_events_{self.get_year()}-{self.get_month():02}.{export_format}"
|
||||
|
||||
|
||||
class UserEventView(SingleTableMixin, ListView):
|
||||
model = EventExt
|
||||
table_class = UserEventTable
|
||||
export_formats = ("csv", "xlsx", "ods")
|
||||
template_name = "membershipworks/user_event_list.dj.html"
|
||||
class InvoiceMoneyColumn(tables.columns.Column):
|
||||
def render(self, value):
|
||||
return f"${super().render(value):.2f}"
|
||||
|
||||
@cached_property
|
||||
def member(self) -> Member | None:
|
||||
return Member.from_user(self.request.user)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.member is None:
|
||||
return self.model.objects.none()
|
||||
class InvoiceMoneyFooterColumn(InvoiceMoneyColumn):
|
||||
def render_footer(self, bound_column, table):
|
||||
value = getattr(table.event, bound_column.accessor)
|
||||
if value is not None:
|
||||
return f"${value:.2f}"
|
||||
else:
|
||||
return super().get_queryset().filter(instructor__member=self.member)
|
||||
|
||||
def get_table_data(self):
|
||||
return (
|
||||
super()
|
||||
.get_table_data()
|
||||
.select_related("category", "instructor", "invoice")
|
||||
.with_financials()
|
||||
)
|
||||
|
||||
def get_export_filename(self, export_format):
|
||||
return f"my_events_{self.member.uid if self.member is not None else 'no-uid'}.{export_format}"
|
||||
return bound_column.default
|
||||
|
||||
|
||||
class EventDetailView(
|
||||
SingleTableMixin, FormMixin, AccessMixin, DetailView, ProcessFormView
|
||||
):
|
||||
class InvoiceTable(tables.Table):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop("event")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
label = tables.Column("Ticket Type", footer="Subtotals")
|
||||
list_price = InvoiceMoneyColumn("Ticket Price")
|
||||
actual_price = InvoiceMoneyColumn("Actual Price [P]")
|
||||
quantity = tables.Column("Quantity [Q]", footer=lambda table: table.event.quantity)
|
||||
amount = InvoiceMoneyFooterColumn("Amount [A = P * Q]")
|
||||
materials = InvoiceMoneyFooterColumn("CMS Collected Materials Fee [M = m * Q]")
|
||||
amount_without_materials = InvoiceMoneyFooterColumn(
|
||||
"Event Revenue Base [R = A - M]"
|
||||
)
|
||||
instructor_fee = InvoiceMoneyFooterColumn("Instructor Fee [F = R * I]")
|
||||
instructor_amount = InvoiceMoneyFooterColumn("Amount Due to Instructor [F + M]")
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
"tbody": {"class": "table-group-divider"},
|
||||
"tfoot": {"class": "table-group-divider"},
|
||||
}
|
||||
orderable = False
|
||||
|
||||
|
||||
class EventInvoiceView(SingleTableMixin, PermissionRequiredMixin, DetailView):
|
||||
permission_required = "membershipworks.view_eventext"
|
||||
queryset = EventExt.objects.with_financials().all()
|
||||
pk_url_kwarg = "eid"
|
||||
context_object_name = "event"
|
||||
template_name = "membershipworks/event_detail.dj.html"
|
||||
template_name = "membershipworks/event_invoice.dj.html"
|
||||
table_pagination = False
|
||||
table_class = InvoiceTable
|
||||
form_class = EventInvoiceForm
|
||||
|
||||
def render_to_response(
|
||||
self, context: dict[str, Any], **response_kwargs: Any
|
||||
) -> HttpResponse:
|
||||
if self.request.user.has_perm(
|
||||
self.permission_required
|
||||
) or self.object.user_is_instructor(self.request.user):
|
||||
return super().render_to_response(context, **response_kwargs)
|
||||
else:
|
||||
return self.handle_no_permission()
|
||||
|
||||
def display_instructor_version(self):
|
||||
return (
|
||||
self.request.method == "POST" # generating a PDF
|
||||
or not self.request.user.has_perm(self.permission_required)
|
||||
or self.request.GET.get("instructor_view")
|
||||
)
|
||||
|
||||
def get_table_data(self):
|
||||
if self.display_instructor_version():
|
||||
return self.object.ticket_types.group_by_ticket_type()
|
||||
else:
|
||||
return self.object.ticket_types.all()
|
||||
return self.object.ticket_types.all()
|
||||
|
||||
def get_table_kwargs(self):
|
||||
kwargs = super().get_table_kwargs()
|
||||
|
||||
kwargs["event"] = self.object
|
||||
if self.display_instructor_version():
|
||||
kwargs["exclude"] = [
|
||||
"list_price",
|
||||
]
|
||||
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.build_absolute_uri()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["user_is_instructor"] = self.object.user_is_instructor(
|
||||
self.request.user
|
||||
)
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["event"] = self.object
|
||||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form: EventInvoiceForm):
|
||||
self.object = self.get_object()
|
||||
event = self.object
|
||||
|
||||
invoice_uuid = uuid.uuid4()
|
||||
|
||||
pdf_context = self.get_context_data(object=event)
|
||||
pdf_context.update({"now": timezone.now(), "invoice_uuid": invoice_uuid})
|
||||
weasy_html = weasyprint.HTML(
|
||||
string=render_to_string(
|
||||
"membershipworks/event_invoice_pdf.dj.html",
|
||||
context=pdf_context,
|
||||
request=self.request,
|
||||
),
|
||||
url_fetcher=django_url_fetcher,
|
||||
base_url="file://",
|
||||
)
|
||||
pdf = weasy_html.write_pdf()
|
||||
# the result will be None only if a target was provided
|
||||
assert pdf is not None
|
||||
|
||||
# NOTE: this is only saved AFTER the emails are successfully sent
|
||||
invoice = EventInvoice(
|
||||
uuid=invoice_uuid,
|
||||
event=event,
|
||||
# NOTE: this needs to be resolved before the object is
|
||||
# saved, so cannot use the Now() db function
|
||||
date_submitted=timezone.now(),
|
||||
amount=event.total_due_to_instructor,
|
||||
)
|
||||
# removed), currently used in event_invoice_admin.dj.html.
|
||||
|
||||
emails = make_invoice_emails(
|
||||
invoice, pdf, self.request.build_absolute_uri(event.get_absolute_url())
|
||||
)
|
||||
with mail.get_connection() as conn:
|
||||
conn.send_messages(emails)
|
||||
|
||||
# this also saves the invoice object
|
||||
invoice.pdf.save(f"{event.eid}.pdf", ContentFile(pdf))
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
"Created Invoice! You should receive a confirmation email shortly.",
|
||||
)
|
||||
|
||||
return super().form_valid(form)
|
||||
return {"event": self.object}
|
||||
|
||||
|
||||
class EventInvoicePDFView(AccessMixin, BaseDetailView):
|
||||
model = EventInvoice
|
||||
pk_url_kwarg = "uuid"
|
||||
|
||||
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
invoice = self.get_object()
|
||||
if request.user.has_perm(
|
||||
"membershipworks.view_eventinvoice"
|
||||
) or invoice.event.user_is_instructor(request.user):
|
||||
# return HttpResponse(invoice.pdf.path)
|
||||
return sendfile(request, invoice.pdf.path, mimetype="application/pdf")
|
||||
|
||||
else:
|
||||
return self.handle_no_permission()
|
||||
class EventAttendeeTable(tables.Table):
|
||||
class Meta:
|
||||
model = EventAttendee
|
||||
fields = ("name", "email")
|
||||
|
||||
|
||||
class EventAttendeeFilters(django_filters.FilterSet):
|
||||
@ -425,6 +368,25 @@ class EventAttendeeListView(
|
||||
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,
|
||||
|
@ -1,92 +0,0 @@
|
||||
import django_tables2 as tables
|
||||
|
||||
from .models import (
|
||||
InstructorOrVendor,
|
||||
Waiver,
|
||||
)
|
||||
|
||||
|
||||
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 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 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 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 CertificationCountTable(tables.Table):
|
||||
certification_version__definition__name = tables.Column("Certification")
|
||||
certification_version__definition__department__name = tables.Column("Department")
|
||||
total_issued = tables.Column()
|
@ -19,6 +19,7 @@ from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFou
|
||||
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
|
||||
@ -34,13 +35,6 @@ from .models import (
|
||||
InstructorOrVendor,
|
||||
Waiver,
|
||||
)
|
||||
from .tables import (
|
||||
AccessVerificationTable,
|
||||
CertificationCountTable,
|
||||
CertifiersTable,
|
||||
InstructorOrVendorTable,
|
||||
WaiverReportTable,
|
||||
)
|
||||
|
||||
WIKI_URL = settings.WIKI_URL
|
||||
|
||||
@ -126,6 +120,34 @@ def certification_pdf(request, cert_name):
|
||||
)
|
||||
|
||||
|
||||
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"
|
||||
@ -134,6 +156,23 @@ class WaiverReport(ExportMixin, SingleTableMixin, PermissionRequiredMixin, ListV
|
||||
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,
|
||||
@ -164,6 +203,33 @@ class InstructorOrVendorReport(
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
@ -251,6 +317,14 @@ 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,
|
||||
@ -282,6 +356,12 @@ class CertifiersReport(
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
|
342
pdm.lock
342
pdm.lock
@ -5,7 +5,7 @@
|
||||
groups = ["default", "debug", "lint", "server", "typing", "dev"]
|
||||
strategy = ["cross_platform"]
|
||||
lock_version = "4.4.1"
|
||||
content_hash = "sha256:d2a8240d2754416c9c2c277832f3b8a2401eae3b56bda6e412aaf285c1c3b955"
|
||||
content_hash = "sha256:0232aa2ec5a45feca37f676076b9caa5f56cf12f411c5c151eb7ef781e508dca"
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
@ -451,7 +451,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.0.4"
|
||||
version = "5.0.2"
|
||||
requires_python = ">=3.10"
|
||||
summary = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
dependencies = [
|
||||
@ -460,26 +460,26 @@ dependencies = [
|
||||
"tzdata; sys_platform == \"win32\"",
|
||||
]
|
||||
files = [
|
||||
{file = "Django-5.0.4-py3-none-any.whl", hash = "sha256:916423499d75d62da7aa038d19aef23d23498d8df229775eb0a6309ee1013775"},
|
||||
{file = "Django-5.0.4.tar.gz", hash = "sha256:4bd01a8c830bb77a8a3b0e7d8b25b887e536ad17a81ba2dce5476135c73312bd"},
|
||||
{file = "Django-5.0.2-py3-none-any.whl", hash = "sha256:56ab63a105e8bb06ee67381d7b65fe6774f057e41a8bab06c8020c8882d8ecd4"},
|
||||
{file = "Django-5.0.2.tar.gz", hash = "sha256:b5bb1d11b2518a5f91372a282f24662f58f66749666b0a286ab057029f728080"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-admin-logs"
|
||||
version = "1.2.0"
|
||||
version = "1.1.0"
|
||||
requires_python = ">=3.6"
|
||||
summary = "View, delete or disable Django admin log entries."
|
||||
dependencies = [
|
||||
"Django>=3.2",
|
||||
]
|
||||
files = [
|
||||
{file = "django-admin-logs-1.2.0.tar.gz", hash = "sha256:4bb69c6e2bfaa7bd47ecf5c13674623e2be3b39c1550f39b2500450e6b2bdc62"},
|
||||
{file = "django_admin_logs-1.2.0-py3-none-any.whl", hash = "sha256:251614a2aa15d5bdd57fe90f9f263d38e6123ea77f52af07393eecdae684e05a"},
|
||||
{file = "django-admin-logs-1.1.0.tar.gz", hash = "sha256:bb87cd944cfa14b6d90c93584fbcdc3ffde9410fd999c65a0b524b94518a5c64"},
|
||||
{file = "django_admin_logs-1.1.0-py3-none-any.whl", hash = "sha256:bb139a99a08a4b08a98731efe9112a6ba269ab0af5efcdba435d1c79706fde16"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-auth-ldap"
|
||||
version = "4.8.0"
|
||||
version = "4.6.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Django LDAP authentication backend"
|
||||
dependencies = [
|
||||
@ -487,8 +487,8 @@ dependencies = [
|
||||
"python-ldap>=3.1",
|
||||
]
|
||||
files = [
|
||||
{file = "django-auth-ldap-4.8.0.tar.gz", hash = "sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738"},
|
||||
{file = "django_auth_ldap-4.8.0-py3-none-any.whl", hash = "sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce"},
|
||||
{file = "django-auth-ldap-4.6.0.tar.gz", hash = "sha256:9ae2bf87f9b6367b6cfd94a0451896cbc728e5400ed81cbfbd58ce743c0909a2"},
|
||||
{file = "django_auth_ldap-4.6.0-py3-none-any.whl", hash = "sha256:4e82ded9292dc6ac7a75784d81b95174b72ca5e650a76e11317e3b68008e56d8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -502,19 +502,6 @@ files = [
|
||||
{file = "django-autocomplete-light-3.11.0.tar.gz", hash = "sha256:212576a17e3308ef7ca77e280b86684167916d2091d4b73640f38845d9516328"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-bootstrap5"
|
||||
version = "24.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Bootstrap 5 for Django"
|
||||
dependencies = [
|
||||
"Django>=3.2",
|
||||
]
|
||||
files = [
|
||||
{file = "django-bootstrap5-24.1.tar.gz", hash = "sha256:fc272b5bb218690fe6f32d52b23c155ebb46fbc5a2856c84eb353c1bf5fc49ea"},
|
||||
{file = "django_bootstrap5-24.1-py3-none-any.whl", hash = "sha256:c42b4f6e673d35af847486733da77104e1e98e7fd5caecc6d56f32a96cc77479"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-db-views"
|
||||
version = "0.1.6"
|
||||
@ -557,15 +544,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "django-filter"
|
||||
version = "24.2"
|
||||
requires_python = ">=3.8"
|
||||
version = "23.5"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
|
||||
dependencies = [
|
||||
"Django>=4.2",
|
||||
"Django>=3.2",
|
||||
]
|
||||
files = [
|
||||
{file = "django-filter-24.2.tar.gz", hash = "sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e"},
|
||||
{file = "django_filter-24.2-py3-none-any.whl", hash = "sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48"},
|
||||
{file = "django-filter-23.5.tar.gz", hash = "sha256:67583aa43b91fe8c49f74a832d95f4d8442be628fd4c6d65e9f811f5153a4e5c"},
|
||||
{file = "django_filter-23.5-py3-none-any.whl", hash = "sha256:99122a201d83860aef4fe77758b69dda913e874cc5e0eaa50a86b0b18d708400"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -633,7 +620,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "django-q2"
|
||||
version = "1.6.2"
|
||||
version = "1.6.1"
|
||||
requires_python = ">=3.8,<4"
|
||||
summary = "A multiprocessing distributed task queue for Django"
|
||||
dependencies = [
|
||||
@ -641,8 +628,8 @@ dependencies = [
|
||||
"django<6,>=3.2",
|
||||
]
|
||||
files = [
|
||||
{file = "django_q2-1.6.2-py3-none-any.whl", hash = "sha256:c2d75552c80b83ca0d8c0b0db7db4f17e9f43ee131a46d0ddd514c5f5fc603cb"},
|
||||
{file = "django_q2-1.6.2.tar.gz", hash = "sha256:cd83c16b5791cd99f83a8d106d2447305d73c6c8ed8ec22c7cb954fe0e814284"},
|
||||
{file = "django_q2-1.6.1-py3-none-any.whl", hash = "sha256:0944b6cbb73671d471fa970a3807e294d750a224764986cff5bff0fe6daa7f7d"},
|
||||
{file = "django_q2-1.6.1.tar.gz", hash = "sha256:38fa67e5f75d172ef59a1edcc891cb5f8f0fb863f95a6ada4c44dd3f2d5bda53"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -659,18 +646,6 @@ files = [
|
||||
{file = "django_recurrence-1.11.1-py3-none-any.whl", hash = "sha256:0c65f30872599b5813a9bab6952dada23c55894f28674490a753ada559f14bc5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-sendfile2"
|
||||
version = "0.7.1"
|
||||
summary = "Abstraction to offload file uploads to web-server (e.g. Apache with mod_xsendfile) once Django has checked permissions etc."
|
||||
dependencies = [
|
||||
"django",
|
||||
]
|
||||
files = [
|
||||
{file = "django-sendfile2-0.7.1.tar.gz", hash = "sha256:b5bec07f1c9b1875a60ea74beb306e9aba964bd8b54f00b4432cb77cc35bc58c"},
|
||||
{file = "django_sendfile2-0.7.1-py3-none-any.whl", hash = "sha256:81971df1db77f688c4834b8540930902d0d929c64cf374bd604b81f5ac52c04a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-stubs"
|
||||
version = "4.2.7"
|
||||
@ -729,20 +704,6 @@ files = [
|
||||
{file = "django_tables2-2.7.0-py2.py3-none-any.whl", hash = "sha256:99e06d966ca8ac69fd74092eb45c79a280dd5ca0ccb81395d96261f62128e1af"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-weasyprint"
|
||||
version = "2.3.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Django WeasyPrint CBV"
|
||||
dependencies = [
|
||||
"Django>=3.2",
|
||||
"WeasyPrint>=53",
|
||||
]
|
||||
files = [
|
||||
{file = "django-weasyprint-2.3.0.tar.gz", hash = "sha256:2f849e15bfd6c1b2a58512097b9042eddf3533651d37d2e096cd6f7d8be6442b"},
|
||||
{file = "django_weasyprint-2.3.0-py3-none-any.whl", hash = "sha256:807cb3b16332123d97c8bbe2ac9c70286103fe353235351803ffd33b67284735"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-widget-tweaks"
|
||||
version = "1.5.0"
|
||||
@ -755,15 +716,16 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "djangorestframework"
|
||||
version = "3.15.1"
|
||||
version = "3.14.0"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Web APIs for Django, made easy."
|
||||
dependencies = [
|
||||
"django>=3.0",
|
||||
"pytz",
|
||||
]
|
||||
files = [
|
||||
{file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"},
|
||||
{file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"},
|
||||
{file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"},
|
||||
{file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1018,7 +980,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "hypothesis"
|
||||
version = "6.100.1"
|
||||
version = "6.98.15"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A library for property-based testing"
|
||||
dependencies = [
|
||||
@ -1026,23 +988,23 @@ dependencies = [
|
||||
"sortedcontainers<3.0.0,>=2.1.0",
|
||||
]
|
||||
files = [
|
||||
{file = "hypothesis-6.100.1-py3-none-any.whl", hash = "sha256:3dacf6ec90e8d14aaee02cde081ac9a17d5b70105e45e6ac822db72052c0195b"},
|
||||
{file = "hypothesis-6.100.1.tar.gz", hash = "sha256:ebff09d7fa4f1fb6a855a812baf17e578b4481b7b70ec6d96496210d1a4c6c35"},
|
||||
{file = "hypothesis-6.98.15-py3-none-any.whl", hash = "sha256:5b40fd81fce9e0b35f0a47e10eb41f375a6b9e8551d0e1084c83b8b0d0d1bb6b"},
|
||||
{file = "hypothesis-6.98.15.tar.gz", hash = "sha256:1e31210951511b24ce8b3b6e04d791c466385a30ac3af571bf2223954b025d77"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hypothesis"
|
||||
version = "6.100.1"
|
||||
version = "6.98.15"
|
||||
extras = ["django"]
|
||||
requires_python = ">=3.8"
|
||||
summary = "A library for property-based testing"
|
||||
dependencies = [
|
||||
"django>=3.2",
|
||||
"hypothesis==6.100.1",
|
||||
"hypothesis==6.98.15",
|
||||
]
|
||||
files = [
|
||||
{file = "hypothesis-6.100.1-py3-none-any.whl", hash = "sha256:3dacf6ec90e8d14aaee02cde081ac9a17d5b70105e45e6ac822db72052c0195b"},
|
||||
{file = "hypothesis-6.100.1.tar.gz", hash = "sha256:ebff09d7fa4f1fb6a855a812baf17e578b4481b7b70ec6d96496210d1a4c6c35"},
|
||||
{file = "hypothesis-6.98.15-py3-none-any.whl", hash = "sha256:5b40fd81fce9e0b35f0a47e10eb41f375a6b9e8551d0e1084c83b8b0d0d1bb6b"},
|
||||
{file = "hypothesis-6.98.15.tar.gz", hash = "sha256:1e31210951511b24ce8b3b6e04d791c466385a30ac3af571bf2223954b025d77"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1057,7 +1019,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "8.23.0"
|
||||
version = "8.22.1"
|
||||
requires_python = ">=3.10"
|
||||
summary = "IPython: Productive Interactive Computing"
|
||||
dependencies = [
|
||||
@ -1070,11 +1032,10 @@ dependencies = [
|
||||
"pygments>=2.4.0",
|
||||
"stack-data",
|
||||
"traitlets>=5.13.0",
|
||||
"typing-extensions; python_version < \"3.12\"",
|
||||
]
|
||||
files = [
|
||||
{file = "ipython-8.23.0-py3-none-any.whl", hash = "sha256:07232af52a5ba146dc3372c7bf52a0f890a23edf38d77caef8d53f9cdc2584c1"},
|
||||
{file = "ipython-8.23.0.tar.gz", hash = "sha256:7468edaf4f6de3e1b912e57f66c241e6fd3c7099f2ec2136e239e142e800274d"},
|
||||
{file = "ipython-8.22.1-py3-none-any.whl", hash = "sha256:869335e8cded62ffb6fac8928e5287a05433d6462e3ebaac25f4216474dd6bc4"},
|
||||
{file = "ipython-8.22.1.tar.gz", hash = "sha256:39c6f9efc079fb19bfb0f17eee903978fe9a290b1b82d68196c641cecb76ea22"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1113,77 +1074,43 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "5.2.1"
|
||||
version = "5.1.0"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
|
||||
files = [
|
||||
{file = "lxml-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:70ac664a48aa64e5e635ae5566f5227f2ab7f66a3990d67566d9907edcbbf867"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae67b4e737cddc96c99461d2f75d218bdf7a0c3d3ad5604d1f5e7464a2f9ffe"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f18a5a84e16886898e51ab4b1d43acb3083c39b14c8caeb3589aabff0ee0b270"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6f2c8372b98208ce609c9e1d707f6918cc118fea4e2c754c9f0812c04ca116d"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394ed3924d7a01b5bd9a0d9d946136e1c2f7b3dc337196d99e61740ed4bc6fe1"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d077bc40a1fe984e1a9931e801e42959a1e6598edc8a3223b061d30fbd26bbc"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764b521b75701f60683500d8621841bec41a65eb739b8466000c6fdbc256c240"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3a6b45da02336895da82b9d472cd274b22dc27a5cea1d4b793874eead23dd14f"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:5ea7b6766ac2dfe4bcac8b8595107665a18ef01f8c8343f00710b85096d1b53a"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e196a4ff48310ba62e53a8e0f97ca2bca83cdd2fe2934d8b5cb0df0a841b193a"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:200e63525948e325d6a13a76ba2911f927ad399ef64f57898cf7c74e69b71095"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dae0ed02f6b075426accbf6b2863c3d0a7eacc1b41fb40f2251d931e50188dad"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ab31a88a651039a07a3ae327d68ebdd8bc589b16938c09ef3f32a4b809dc96ef"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:df2e6f546c4df14bc81f9498bbc007fbb87669f1bb707c6138878c46b06f6510"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5dd1537e7cc06efd81371f5d1a992bd5ab156b2b4f88834ca852de4a8ea523fa"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b9ec9c9978b708d488bec36b9e4c94d88fd12ccac3e62134a9d17ddba910ea9"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8e77c69d5892cb5ba71703c4057091e31ccf534bd7f129307a4d084d90d014b8"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d5c70e04aac1eda5c829a26d1f75c6e5286c74743133d9f742cda8e53b9c2f"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c94e75445b00319c1fad60f3c98b09cd63fe1134a8a953dcd48989ef42318534"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-win32.whl", hash = "sha256:4951e4f7a5680a2db62f7f4ab2f84617674d36d2d76a729b9a8be4b59b3659be"},
|
||||
{file = "lxml-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c670c0406bdc845b474b680b9a5456c561c65cf366f8db5a60154088c92d102"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:abc25c3cab9ec7fcd299b9bcb3b8d4a1231877e425c650fa1c7576c5107ab851"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6935bbf153f9a965f1e07c2649c0849d29832487c52bb4a5c5066031d8b44fd5"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d793bebb202a6000390a5390078e945bbb49855c29c7e4d56a85901326c3b5d9"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd5562927cdef7c4f5550374acbc117fd4ecc05b5007bdfa57cc5355864e0a4"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e7259016bc4345a31af861fdce942b77c99049d6c2107ca07dc2bba2435c1d9"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:530e7c04f72002d2f334d5257c8a51bf409db0316feee7c87e4385043be136af"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59689a75ba8d7ffca577aefd017d08d659d86ad4585ccc73e43edbfc7476781a"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f9737bf36262046213a28e789cc82d82c6ef19c85a0cf05e75c670a33342ac2c"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:3a74c4f27167cb95c1d4af1c0b59e88b7f3e0182138db2501c353555f7ec57f4"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:68a2610dbe138fa8c5826b3f6d98a7cfc29707b850ddcc3e21910a6fe51f6ca0"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a1bc63a465b6d72569a9bba9f2ef0334c4e03958e043da1920299100bc7c08"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d35a1d047efd68027817b32ab1586c1169e60ca02c65d428ae815b593e65d4"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:79bd05260359170f78b181b59ce871673ed01ba048deef4bf49a36ab3e72e80b"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:865bad62df277c04beed9478fe665b9ef63eb28fe026d5dedcb89b537d2e2ea6"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71e97313406ccf55d32cc98a533ee05c61e15d11b99215b237346171c179c0b0"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:057cdc6b86ab732cf361f8b4d8af87cf195a1f6dc5b0ff3de2dced242c2015e0"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f3bbbc998d42f8e561f347e798b85513ba4da324c2b3f9b7969e9c45b10f6169"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491755202eb21a5e350dae00c6d9a17247769c64dcf62d8c788b5c135e179dc4"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-win32.whl", hash = "sha256:8de8f9d6caa7f25b204fc861718815d41cbcf27ee8f028c89c882a0cf4ae4134"},
|
||||
{file = "lxml-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:f2a9efc53d5b714b8df2b4b3e992accf8ce5bbdfe544d74d5c6766c9e1146a3a"},
|
||||
{file = "lxml-5.2.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b0ff53900566bc6325ecde9181d89afadc59c5ffa39bddf084aaedfe3b06a11"},
|
||||
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6037392f2d57793ab98d9e26798f44b8b4da2f2464388588f48ac52c489ea1"},
|
||||
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9c07e7a45bb64e21df4b6aa623cb8ba214dfb47d2027d90eac197329bb5e94"},
|
||||
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3249cc2989d9090eeac5467e50e9ec2d40704fea9ab72f36b034ea34ee65ca98"},
|
||||
{file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f42038016852ae51b4088b2862126535cc4fc85802bfe30dea3500fdfaf1864e"},
|
||||
{file = "lxml-5.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:533658f8fbf056b70e434dff7e7aa611bcacb33e01f75de7f821810e48d1bb66"},
|
||||
{file = "lxml-5.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:622020d4521e22fb371e15f580d153134bfb68d6a429d1342a25f051ec72df1c"},
|
||||
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7b51824aa0ee957ccd5a741c73e6851de55f40d807f08069eb4c5a26b2baa"},
|
||||
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c6ad0fbf105f6bcc9300c00010a2ffa44ea6f555df1a2ad95c88f5656104817"},
|
||||
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e233db59c8f76630c512ab4a4daf5a5986da5c3d5b44b8e9fc742f2a24dbd460"},
|
||||
{file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a014510830df1475176466b6087fc0c08b47a36714823e58d8b8d7709132a96"},
|
||||
{file = "lxml-5.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d38c8f50ecf57f0463399569aa388b232cf1a2ffb8f0a9a5412d0db57e054860"},
|
||||
{file = "lxml-5.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5aea8212fb823e006b995c4dda533edcf98a893d941f173f6c9506126188860d"},
|
||||
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff097ae562e637409b429a7ac958a20aab237a0378c42dabaa1e3abf2f896e5f"},
|
||||
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f5d65c39f16717a47c36c756af0fb36144069c4718824b7533f803ecdf91138"},
|
||||
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b"},
|
||||
{file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e32be23d538753a8adb6c85bd539f5fd3b15cb987404327c569dfc5fd8366e85"},
|
||||
{file = "lxml-5.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cc518cea79fd1e2f6c90baafa28906d4309d24f3a63e801d855e7424c5b34144"},
|
||||
{file = "lxml-5.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0af35bd8ebf84888373630f73f24e86bf016642fb8576fba49d3d6b560b7cbc"},
|
||||
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8aca2e3a72f37bfc7b14ba96d4056244001ddcc18382bd0daa087fd2e68a354"},
|
||||
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ca1e8188b26a819387b29c3895c47a5e618708fe6f787f3b1a471de2c4a94d9"},
|
||||
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c8ba129e6d3b0136a0f50345b2cb3db53f6bda5dd8c7f5d83fbccba97fb5dcb5"},
|
||||
{file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e998e304036198b4f6914e6a1e2b6f925208a20e2042563d9734881150c6c246"},
|
||||
{file = "lxml-5.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d3be9b2076112e51b323bdf6d5a7f8a798de55fb8d95fcb64bd179460cdc0704"},
|
||||
{file = "lxml-5.2.1.tar.gz", hash = "sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"},
|
||||
{file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"},
|
||||
{file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"},
|
||||
{file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"},
|
||||
{file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"},
|
||||
{file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"},
|
||||
{file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"},
|
||||
{file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"},
|
||||
{file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"},
|
||||
{file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"},
|
||||
{file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"},
|
||||
{file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"},
|
||||
{file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"},
|
||||
{file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"},
|
||||
{file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"},
|
||||
{file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1220,15 +1147,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "markdownify"
|
||||
version = "0.12.1"
|
||||
version = "0.11.6"
|
||||
summary = "Convert HTML to markdown."
|
||||
dependencies = [
|
||||
"beautifulsoup4<5,>=4.9",
|
||||
"six<2,>=1.15",
|
||||
]
|
||||
files = [
|
||||
{file = "markdownify-0.12.1-py3-none-any.whl", hash = "sha256:a3805abd8166dbb7b27783c5599d91f54f10d79894b2621404d85b333c7ce561"},
|
||||
{file = "markdownify-0.12.1.tar.gz", hash = "sha256:1fb08c618b30e0ee7a31a39b998f44a18fb28ab254f55f4af06b6d35a2179e27"},
|
||||
{file = "markdownify-0.11.6-py3-none-any.whl", hash = "sha256:ba35fe289d5e9073bcd7d2cad629278fe25f1a93741fcdc0bfb4f009076d8324"},
|
||||
{file = "markdownify-0.11.6.tar.gz", hash = "sha256:009b240e0c9f4c8eaf1d085625dcd4011e12f0f8cec55dedf9ea6f7655e49bfe"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1384,25 +1311,25 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "nh3"
|
||||
version = "0.2.17"
|
||||
version = "0.2.15"
|
||||
summary = "Python bindings to the ammonia HTML sanitization library."
|
||||
files = [
|
||||
{file = "nh3-0.2.17-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-win32.whl", hash = "sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911"},
|
||||
{file = "nh3-0.2.17-cp37-abi3-win_amd64.whl", hash = "sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb"},
|
||||
{file = "nh3-0.2.17.tar.gz", hash = "sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"},
|
||||
{file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"},
|
||||
{file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1663,6 +1590,15 @@ files = [
|
||||
{file = "python-ldap-3.4.3.tar.gz", hash = "sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2022.7"
|
||||
summary = "World timezone definitions, modern and historical"
|
||||
files = [
|
||||
{file = "pytz-2022.7-py2.py3-none-any.whl", hash = "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"},
|
||||
{file = "pytz-2022.7.tar.gz", hash = "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "306"
|
||||
@ -1781,27 +1717,27 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.3.7"
|
||||
version = "0.3.0"
|
||||
requires_python = ">=3.7"
|
||||
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
files = [
|
||||
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"},
|
||||
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
|
||||
{file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
|
||||
{file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
|
||||
{file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
|
||||
{file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
|
||||
{file = "ruff-0.3.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7deb528029bacf845bdbb3dbb2927d8ef9b4356a5e731b10eef171e3f0a85944"},
|
||||
{file = "ruff-0.3.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e1e0d4381ca88fb2b73ea0766008e703f33f460295de658f5467f6f229658c19"},
|
||||
{file = "ruff-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f7dbba46e2827dfcb0f0cc55fba8e96ba7c8700e0a866eb8cef7d1d66c25dcb"},
|
||||
{file = "ruff-0.3.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23dbb808e2f1d68eeadd5f655485e235c102ac6f12ad31505804edced2a5ae77"},
|
||||
{file = "ruff-0.3.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ef655c51f41d5fa879f98e40c90072b567c666a7114fa2d9fe004dffba00932"},
|
||||
{file = "ruff-0.3.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d0d3d7ef3d4f06433d592e5f7d813314a34601e6c5be8481cccb7fa760aa243e"},
|
||||
{file = "ruff-0.3.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b08b356d06a792e49a12074b62222f9d4ea2a11dca9da9f68163b28c71bf1dd4"},
|
||||
{file = "ruff-0.3.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9343690f95710f8cf251bee1013bf43030072b9f8d012fbed6ad702ef70d360a"},
|
||||
{file = "ruff-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1f3ed501a42f60f4dedb7805fa8d4534e78b4e196f536bac926f805f0743d49"},
|
||||
{file = "ruff-0.3.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cc30a9053ff2f1ffb505a585797c23434d5f6c838bacfe206c0e6cf38c921a1e"},
|
||||
{file = "ruff-0.3.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5da894a29ec018a8293d3d17c797e73b374773943e8369cfc50495573d396933"},
|
||||
{file = "ruff-0.3.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:755c22536d7f1889be25f2baf6fedd019d0c51d079e8417d4441159f3bcd30c2"},
|
||||
{file = "ruff-0.3.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd73fe7f4c28d317855da6a7bc4aa29a1500320818dd8f27df95f70a01b8171f"},
|
||||
{file = "ruff-0.3.0-py3-none-win32.whl", hash = "sha256:19eacceb4c9406f6c41af806418a26fdb23120dfe53583df76d1401c92b7c14b"},
|
||||
{file = "ruff-0.3.0-py3-none-win_amd64.whl", hash = "sha256:128265876c1d703e5f5e5a4543bd8be47c73a9ba223fd3989d4aa87dd06f312f"},
|
||||
{file = "ruff-0.3.0-py3-none-win_arm64.whl", hash = "sha256:e3a4a6d46aef0a84b74fcd201a4401ea9a6cd85614f6a9435f2d33dd8cefbf83"},
|
||||
{file = "ruff-0.3.0.tar.gz", hash = "sha256:0886184ba2618d815067cf43e005388967b67ab9c80df52b32ec1152ab49f53a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1816,12 +1752,12 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "69.5.1"
|
||||
version = "69.1.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
files = [
|
||||
{file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
|
||||
{file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"},
|
||||
{file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"},
|
||||
{file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1889,28 +1825,28 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tablib"
|
||||
version = "3.6.1"
|
||||
version = "3.5.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)"
|
||||
files = [
|
||||
{file = "tablib-3.6.1-py3-none-any.whl", hash = "sha256:c771d38ed1d74350a69873db43e0afb7f1cca0ed2915a7243094463eb6789207"},
|
||||
{file = "tablib-3.6.1.tar.gz", hash = "sha256:040685fde11e9237675f43e985edb94b63250a5e9236f89d561ce6fb1465b839"},
|
||||
{file = "tablib-3.5.0-py3-none-any.whl", hash = "sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9"},
|
||||
{file = "tablib-3.5.0.tar.gz", hash = "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tablib"
|
||||
version = "3.6.1"
|
||||
version = "3.5.0"
|
||||
extras = ["ods", "xlsx"]
|
||||
requires_python = ">=3.8"
|
||||
summary = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)"
|
||||
dependencies = [
|
||||
"odfpy",
|
||||
"openpyxl>=2.6.0",
|
||||
"tablib==3.6.1",
|
||||
"tablib==3.5.0",
|
||||
]
|
||||
files = [
|
||||
{file = "tablib-3.6.1-py3-none-any.whl", hash = "sha256:c771d38ed1d74350a69873db43e0afb7f1cca0ed2915a7243094463eb6789207"},
|
||||
{file = "tablib-3.6.1.tar.gz", hash = "sha256:040685fde11e9237675f43e985edb94b63250a5e9236f89d561ce6fb1465b839"},
|
||||
{file = "tablib-3.5.0-py3-none-any.whl", hash = "sha256:9821caa9eca6062ff7299fa645e737aecff982e6b2b42046928a6413c8dabfd9"},
|
||||
{file = "tablib-3.5.0.tar.gz", hash = "sha256:f6661dfc45e1d4f51fa8a6239f9c8349380859a5bfaa73280645f046d6c96e33"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1961,15 +1897,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "types-bleach"
|
||||
version = "6.1.0.20240331"
|
||||
version = "6.1.0.20240222"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Typing stubs for bleach"
|
||||
dependencies = [
|
||||
"types-html5lib",
|
||||
]
|
||||
files = [
|
||||
{file = "types-bleach-6.1.0.20240331.tar.gz", hash = "sha256:2ee858a84fb06fc2225ff56ba2f7f6c88b65638659efae0d7bfd6b24a1b5a524"},
|
||||
{file = "types_bleach-6.1.0.20240331-py3-none-any.whl", hash = "sha256:399bc59bfd20a36a56595f13f805e56c8a08e5a5c07903e5cf6fafb5a5107dd4"},
|
||||
{file = "types-bleach-6.1.0.20240222.tar.gz", hash = "sha256:1299f06b5ef80e2d4f25fac11f613033c9bc35bad116413cb320d0c0c1188466"},
|
||||
{file = "types_bleach-6.1.0.20240222-py3-none-any.whl", hash = "sha256:78a1c39484c8949030a0931076eeb69b03a0e08a4fafec5b3764e142929859d4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2002,15 +1938,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.31.0.20240406"
|
||||
version = "2.31.0.20240218"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Typing stubs for requests"
|
||||
dependencies = [
|
||||
"urllib3>=2",
|
||||
]
|
||||
files = [
|
||||
{file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"},
|
||||
{file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"},
|
||||
{file = "types-requests-2.31.0.20240218.tar.gz", hash = "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"},
|
||||
{file = "types_requests-2.31.0.20240218-py3-none-any.whl", hash = "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2071,7 +2007,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.29.0"
|
||||
version = "0.27.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "The lightning-fast ASGI server."
|
||||
dependencies = [
|
||||
@ -2079,13 +2015,13 @@ dependencies = [
|
||||
"h11>=0.8",
|
||||
]
|
||||
files = [
|
||||
{file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
|
||||
{file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
|
||||
{file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"},
|
||||
{file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.29.0"
|
||||
version = "0.27.1"
|
||||
extras = ["standard"]
|
||||
requires_python = ">=3.8"
|
||||
summary = "The lightning-fast ASGI server."
|
||||
@ -2094,14 +2030,14 @@ dependencies = [
|
||||
"httptools>=0.5.0",
|
||||
"python-dotenv>=0.13",
|
||||
"pyyaml>=5.1",
|
||||
"uvicorn==0.29.0",
|
||||
"uvicorn==0.27.1",
|
||||
"uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"",
|
||||
"watchfiles>=0.13",
|
||||
"websockets>=10.4",
|
||||
]
|
||||
files = [
|
||||
{file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
|
||||
{file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
|
||||
{file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"},
|
||||
{file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2186,7 +2122,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "weasyprint"
|
||||
version = "61.2"
|
||||
version = "61.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "The Awesome Document Factory"
|
||||
dependencies = [
|
||||
@ -2200,8 +2136,8 @@ dependencies = [
|
||||
"tinycss2>=1.0.0",
|
||||
]
|
||||
files = [
|
||||
{file = "weasyprint-61.2-py3-none-any.whl", hash = "sha256:76c6dc0e75e09182d5645d92c66ddf86b1b992c9420235b723fb374b584e5bf4"},
|
||||
{file = "weasyprint-61.2.tar.gz", hash = "sha256:47df6cfeeff8c6c28cf2e4caf837cde17715efe462708ada74baa2eb391b6059"},
|
||||
{file = "weasyprint-61.1-py3-none-any.whl", hash = "sha256:a5622f2c67d408e5d2e94df0ae07b9bd0fa9bb5e1ee466e2211391807f1ece41"},
|
||||
{file = "weasyprint-61.1.tar.gz", hash = "sha256:7cbc824dc4026d82a97362647755f837dd15799949639cc8a428dd33d385009a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7,23 +7,23 @@ authors = [
|
||||
]
|
||||
dependencies = [
|
||||
"django~=5.0",
|
||||
"django-admin-logs~=1.2",
|
||||
"django-auth-ldap~=4.8",
|
||||
"django-admin-logs~=1.1",
|
||||
"django-auth-ldap~=4.6",
|
||||
"django-markdownx~=4.0",
|
||||
"django-recurrence~=1.11",
|
||||
"django-widget-tweaks~=1.5",
|
||||
"django-stubs-ext~=4.2",
|
||||
"markdownify~=0.12",
|
||||
"markdownify~=0.11",
|
||||
"mdformat~=0.7",
|
||||
"mdformat-tables~=0.4",
|
||||
"mysqlclient~=2.2",
|
||||
"django-autocomplete-light~=3.11",
|
||||
"weasyprint~=61.2",
|
||||
"weasyprint~=61.1",
|
||||
"requests~=2.31",
|
||||
"semver~=3.0",
|
||||
"djangorestframework~=3.15",
|
||||
"djangorestframework~=3.14",
|
||||
"django-q2~=1.6",
|
||||
"lxml~=5.2",
|
||||
"lxml~=5.1",
|
||||
"django-object-actions~=4.2",
|
||||
"bitstring~=4.1",
|
||||
"udm-rest-client~=1.2",
|
||||
@ -31,20 +31,17 @@ dependencies = [
|
||||
"django-nh3~=0.1",
|
||||
"nh3~=0.2",
|
||||
"django-tables2~=2.7",
|
||||
"tablib[ods,xlsx]~=3.6",
|
||||
"django-filter~=24.2",
|
||||
"tablib[ods,xlsx]~=3.5",
|
||||
"django-filter~=23.5",
|
||||
"django-db-views~=0.1",
|
||||
"django-mysql~=4.12",
|
||||
"django-weasyprint~=2.3",
|
||||
"django-sendfile2~=0.7",
|
||||
"django-bootstrap5~=24.1",
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[project.optional-dependencies]
|
||||
server = [
|
||||
"uvicorn[standard]~=0.29",
|
||||
"setuptools~=69.5",
|
||||
"uvicorn[standard]~=0.27",
|
||||
"setuptools~=69.1",
|
||||
]
|
||||
|
||||
[project.entry-points."djangoq.errorreporters"]
|
||||
@ -54,7 +51,7 @@ admin_email = "cmsmanage.django_q2_admin_email_reporter:AdminEmailReporter"
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E4", "E7", "E9", "F", "I", "C4", "UP", "PERF", "PL", "SIM", "FIX003"]
|
||||
select = ["E4", "E7", "E9", "F", "I", "C4", "UP", "PERF", "PL", "SIM"]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = [
|
||||
@ -116,7 +113,7 @@ lint = [
|
||||
typing = [
|
||||
"mypy~=1.7",
|
||||
"django-stubs~=4.2",
|
||||
"setuptools~=69.5",
|
||||
"setuptools~=69.1",
|
||||
"types-bleach~=6.1",
|
||||
"types-requests~=2.31",
|
||||
"types-urllib3~=1.26",
|
||||
@ -128,14 +125,14 @@ debug = [
|
||||
]
|
||||
dev = [
|
||||
"django-extensions~=3.2",
|
||||
"ipython~=8.23",
|
||||
"hypothesis[django]~=6.100",
|
||||
"ipython~=8.22",
|
||||
"hypothesis[django]~=6.98",
|
||||
"tblib~=3.0",
|
||||
]
|
||||
|
||||
[tool.pdm.scripts]
|
||||
start = "./manage.py runserver"
|
||||
fmt.shell = "ruff check --fix ; ruff format . ; djlint --reformat ."
|
||||
fmt.shell = "ruff check --fix && ruff format . && djlint --reformat ."
|
||||
|
||||
[build-system]
|
||||
requires = ["pdm-backend"]
|
||||
|
Loading…
Reference in New Issue
Block a user