Compare commits

..

10 Commits

13 changed files with 216 additions and 372 deletions

View File

@ -10,7 +10,9 @@ jobs:
container: catthehacker/ubuntu:act-latest
services:
mariadb:
image: mariadb:latest
# TODO: this is pinned to avoid what apears to be a bug with
# MariaDB >= 10.11.9, and collation issues with 11.x.x
image: mariadb:10.11.8
env:
MARIADB_ROOT_PASSWORD: whatever
healthcheck:
@ -36,5 +38,15 @@ jobs:
sudo apt-get -y install build-essential python3-dev libldap2-dev libsasl2-dev mariadb-client
- name: Install python dependencies
run: pdm sync -d -G dev
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest
run_install: |
- args: [--frozen-lockfile, --strict-peer-dependencies]
- name: Build JS assets
run: pnpm run build
- name: Run tests
run: pdm run -v ./manage.py test

View File

@ -357,6 +357,12 @@ class CI(Base):
configure_hypothesis_profiles()
settings.load_profile("ci")
@property
def DJANGO_VITE(self):
d = super().DJANGO_VITE
d["default"]["manifest_path"] = BASE_DIR / "vite-dist" / "manifest.json"
return d
SECRET_KEY = "aed7jee2kai1we9eithae0gaegh9ohthoh4phahk5bau4Ahxaijo3aicheex3qua"
DATABASES = {

View File

@ -152,7 +152,7 @@ class DoorController:
)
return self.doXMLRequest(el)
def get_records(self, req, count, params={}, stopFunction=None):
def get_records(self, req, count, params=None, stopFunction=None):
recordCount = 0
moreRecords = True
@ -172,7 +172,7 @@ class DoorController:
"recordOffset": str(
recordCount - 1 if recordCount > 0 else 0
),
**params,
**(params or {}),
}
)
)

View File

@ -1,292 +0,0 @@
from unittest.mock import patch
import pytest
import responses
from lxml.etree import Element
from ..DoorController import ROOT, DoorController, E, E_corp, RemoteError
# https://stackoverflow.com/questions/7905380/testing-equivalence-of-xml-etree-elementtree
def assert_elements_equal(e1: Element, e2: Element) -> None:
assert e1.tag == e2.tag
assert e1.text == e2.text
assert e1.tail == e2.tail
assert e1.attrib == e2.attrib
assert len(e1) == len(e2)
for c1, c2 in zip(e1, e2):
assert_elements_equal(c1, c2)
@pytest.fixture
def door_controller():
return DoorController("127.0.0.1", "test", "test", name="Test", access="Test")
@responses.activate
def test_doXMLRequest_bytes(door_controller: DoorController) -> None:
responses.add(
responses.GET,
"https://127.0.0.1/cgi-bin/vertx_xml.cgi",
body='<?xml version="1.0" encoding="UTF-8"?><VertXMessage xmlns:hid="http://www.hidcorp.com/VertX"><hid:Banana></hid:Banana></VertXMessage>',
content_type="text/xml",
)
ret = door_controller.doXMLRequest(
b"<VertXMessage><hid:TEST></hid:TEST></VertXMessage>"
)
assert (
responses.calls[0].request.params["/cgi-bin/vertx_xml.cgi?XML"]
== '<?xml version="1.0" encoding="UTF-8"?><VertXMessage><hid:TEST></hid:TEST></VertXMessage>'
)
assert_elements_equal(ret, ROOT(E_corp.Banana()))
@responses.activate
def test_doXMLRequest_xml(door_controller: DoorController) -> None:
responses.add(
responses.GET,
"https://127.0.0.1/cgi-bin/vertx_xml.cgi",
body='<?xml version="1.0" encoding="UTF-8"?><VertXMessage xmlns:hid="http://www.hidcorp.com/VertX"><hid:Banana></hid:Banana></VertXMessage>',
content_type="text/xml",
)
ret = door_controller.doXMLRequest(ROOT(E.TEST()))
assert (
responses.calls[0].request.params["/cgi-bin/vertx_xml.cgi?XML"]
== '<?xml version="1.0" encoding="UTF-8"?><VertXMessage xmlns:hid="http://www.hidglobal.com/VertX"><hid:TEST/></VertXMessage>'
)
assert_elements_equal(ret, ROOT(E_corp.Banana()))
@responses.activate
def test_doXMLRequest_HTTPError(door_controller: DoorController) -> None:
responses.add(
responses.GET,
"https://127.0.0.1/cgi-bin/vertx_xml.cgi",
body="whatever",
status=403,
)
with pytest.raises(RemoteError) as excinfo:
door_controller.doXMLRequest(ROOT(E.TEST()))
assert excinfo.value.args[0] == "Door Updating Error: 403 Forbidden\nwhatever"
@responses.activate
def test_doXMLRequest_XMLerror(door_controller: DoorController) -> None:
body = '<?xml version="1.0" encoding="UTF-8"?><VertXMessage xmlns:hid="http://www.hidcorp.com/VertX"><hid:Error action="RS" elementType="hid:TEST" errorCode="72" errorReporter="vertx" errorMessage="Unrecognized element"/></VertXMessage>'
responses.add(
responses.GET,
"https://127.0.0.1/cgi-bin/vertx_xml.cgi",
body=body,
status=200,
)
with pytest.raises(RemoteError) as excinfo:
door_controller.doXMLRequest(ROOT(E.TEST()))
assert excinfo.value.args[0] == "Door Updating Error: 200 OK\n" + body
# def doImport(self, params=None, files=None):
# def doCSVImport(self, csv):
def test_get_scheduleMap(door_controller: DoorController) -> None:
with patch.object(door_controller, "doXMLRequest") as mockXMLRequest:
mockXMLRequest.return_value = E_corp.VertXMessage(
E_corp.Schedules(
{"action": "RL"},
E_corp.Schedule({"scheduleID": "1", "scheduleName": "Test1"}),
E_corp.Schedule({"scheduleID": "2", "scheduleName": "Test2"}),
E_corp.Schedule({"scheduleID": "3", "scheduleName": "Test3"}),
)
)
ret = door_controller.get_scheduleMap()
assert ret == {"Test1": "1", "Test2": "2", "Test3": "3"}
# TODO: these two methods might want to be reworked: they are a bit clunky
# def get_schedules(self):
# def set_schedules(self, schedules):
def test_set_cardholder_schedules(door_controller: DoorController) -> None:
with patch.object(door_controller, "doXMLRequest") as mockXMLRequest:
door_controller._scheduleMap = {"Test1": "1", "Test2": "2", "Test3": "3"}
# TODO: should replace with a captured output
mockXMLRequest.return_value = ROOT()
ret = door_controller.set_cardholder_schedules("123", ["Test1", "Test3"])
assert_elements_equal(
door_controller.doXMLRequest.call_args[0][0],
ROOT(
E.RoleSet(
{"action": "UD", "roleSetID": "123"},
E.Roles(
E.Role({"roleID": "123", "scheduleID": "1", "resourceID": "0"}),
E.Role({"roleID": "123", "scheduleID": "3", "resourceID": "0"}),
),
)
),
)
assert_elements_equal(ret, ROOT())
def test_get_cardFormats(door_controller):
with patch.object(door_controller, "doXMLRequest") as mockXMLRequest:
mockXMLRequest.return_value = E_corp.VertXMessage(
E_corp.CardFormats(
{"action": "RL"},
E_corp.CardFormat(
{
"formatID": "1",
"formatName": "H10301 26-Bit",
"isTemplate": "true",
"templateID": "1",
}
),
# irrelevant templates omitted
E_corp.CardFormat(
{
"formatID": "6",
"formatName": "A901146A-123",
"isTemplate": "false",
"templateID": "1",
},
E_corp.FixedField({"value": "123"}),
),
E_corp.CardFormat(
{
"formatID": "7",
"formatName": "A901146A-456",
"isTemplate": "false",
"templateID": "1",
},
E_corp.FixedField({"value": "456"}),
),
)
)
ret = door_controller.get_cardFormats()
assert ret == {"123": "6", "456": "7"}
def test_set_cardFormat(door_controller):
with patch.object(door_controller, "doXMLRequest") as mockXMLRequest:
# TODO: should replace with a captured output
mockXMLRequest.return_value = ROOT()
ret = door_controller.set_cardFormat("testname", 3, 123)
assert_elements_equal(
door_controller.doXMLRequest.call_args[0][0],
ROOT(
E.CardFormats(
{"action": "AD"},
E.CardFormat(
{"formatName": "testname", "templateID": "3"},
E.FixedField({"value": "123"}),
),
)
),
)
assert_elements_equal(ret, ROOT())
def test_get_records_no_morerecords(door_controller):
"""Test for when all the records fit in one 'page'"""
with patch.object(door_controller, "doXMLRequest") as mockXMLRequest:
mockXMLRequest.return_value = E_corp.VertXMessage(
E_corp.TestElements(
{
"action": "RL",
"recordOffset": "0",
"recordCount": "2",
"moreRecords": "false",
},
E_corp.TestElement({"asdf": "a"}),
E_corp.TestElement({"qwer": "b"}),
)
)
ret = door_controller.get_records(E.TestElements, 12, {"blah": "test"})
assert_elements_equal(
door_controller.doXMLRequest.call_args[0][0],
ROOT(
E.TestElements(
{
"action": "LR",
# TODO: should really be 12, but isn't for bug workaround
"recordCount": "13",
"recordOffset": "0",
"blah": "test",
},
)
),
)
assert_elements_equal(ret[0], E_corp.TestElement({"asdf": "a"}))
assert_elements_equal(ret[1], E_corp.TestElement({"qwer": "b"}))
# def test_get_records_morerecords(door_controller):
# """Test for when all the records span multiple 'pages'"""
# pass
# def test_get_records_morerecords_bad_last_record(door_controller):
# """Test for bug in which last record of each 'page' is missing data"""
# pass
# def test_get_records_stopFunction(door_controller):
# pass
# def test_get_cardholders(door_controller):
# door_controller = DoorController(
# "172.18.51.11",
# "admin",
# "PVic6ydFS/",
# name="Test",
# access="Test",
# cert="../../hidglobal.com.pem",
# )
# with patch.object(door_controller, "doXMLRequest") as mockXMLRequest:
# mockXMLRequest.return_value = E_corp.VertXMessage(
# E_corp.Cardholders(
# {"action": "RL"},
# E_corp.Cardholder({"scheduleID": "1", "scheduleName": "Test1"}),
# E_corp.Cardholder({"scheduleID": "2", "scheduleName": "Test2"}),
# E_corp.Cardholder({"scheduleID": "3", "scheduleName": "Test3"}),
# )
# )
# for x in [0]:
# ret = door_controller.get_cardholders()
# assert ret == {"Test1": "1", "Test2": "2", "Test3": "3"}
# def add_cardholder(self, attribs):
# def update_cardholder(self, cardholderID, attribs):
# def get_credentials(self):
# def add_credentials(self, credentials, cardholderID=None):
# def assign_credential(self, credential, cardholderID=None):
# def get_events(self, threshold):
# def get_lock(self):
# def set_lock(self, lock=True):

View File

@ -136,7 +136,7 @@ class DoorMember:
self,
existing_door_credentials: set[Credential],
all_members: list["DoorMember"],
old_credentials: set[Credential] = set(),
old_credentials: set[Credential],
):
# cardholderID should be set on a member before this is called
assert self.cardholderID is not None
@ -240,7 +240,7 @@ def update_door(door: Door, dry_run: bool = False):
]
member.update_attribs()
member.update_credentials(existing_door_credentials, members)
member.update_credentials(existing_door_credentials, members, set())
member.update_schedules()
# cardholder exists, compare contents

View File

@ -590,7 +590,7 @@ class EventExt(Event):
self.materials_fee_included_in_price is not None
or self.materials_fee == 0
)
and getattr(self, "total_due_to_instructor") is not None
and self.total_due_to_instructor is not None
)
@ -762,7 +762,7 @@ class EventTicketType(DBView):
objects = EventTicketTypeManager.from_queryset(EventTicketTypeQuerySet)()
event = models.ForeignKey(
EventExt, on_delete=models.CASCADE, related_name="ticket_types"
EventExt, on_delete=models.DO_NOTHING, related_name="ticket_types"
)
label = models.TextField()
restrict_to = models.TextField(null=True, blank=True)
@ -807,7 +807,7 @@ class EventTicketType(DBView):
class EventAttendeeStats(DBView):
event = models.ForeignKey(
EventExt, on_delete=models.CASCADE, related_name="attendee_stats"
EventExt, on_delete=models.DO_NOTHING, related_name="attendee_stats"
)
gross_revenue = models.FloatField()
@ -827,7 +827,7 @@ class EventAttendeeStats(DBView):
class EventAttendee(DBView):
event = models.ForeignKey(
EventExt, on_delete=models.CASCADE, related_name="attendees"
EventExt, on_delete=models.DO_NOTHING, related_name="attendees"
)
uid = models.ForeignKey(Member, on_delete=models.DO_NOTHING)
name = models.CharField(max_length=256)

View File

@ -81,7 +81,9 @@ def scrape_transactions(membershipworks: MembershipWorks):
transactions_csv = membershipworks.get_transactions(start_date, now)
transactions_json = membershipworks.get_transactions(start_date, now, json=True)
# this is terrible, but as long as the dates are the same, should be fiiiine
transactions = [{**j, **v} for j, v in zip(transactions_csv, transactions_json)]
transactions = [
{**j, **v} for j, v in zip(transactions_csv, transactions_json, strict=True)
]
assert all(
t["Account ID"] == t.get("uid", "") and t["Payment ID"] == t.get("sid", "")
for t in transactions
@ -176,3 +178,6 @@ def scrape_events():
event_ext.details = membershipworks.get_event_by_eid(event.eid)
event_ext.registrations = membershipworks.get_event_registrations(event.eid)
event_ext.save()
# delete all events that did not occur in the event list
EventExt.objects.exclude(pk__in=events).delete()

View File

@ -1 +1,66 @@
# Create your tests here.
from datetime import datetime
from django.db.models.functions import TruncYear
from django.test import TestCase
from django.utils import timezone
from membershipworks.models import (
Event,
EventCategory,
EventExt,
EventInstructor,
EventTicketType,
Flag,
)
class EventFinancials(TestCase):
def setUp(self) -> None:
members_folder = Flag.objects.create(
id="members_folder_flag", name="Members", type="folder"
)
instructor = EventInstructor.objects.create(name="instructor1")
category = EventCategory.objects.create(id=1, title="cat1")
EventExt.objects.create(
eid="event1",
url="",
title="Test Event 1",
instructor=instructor,
materials_fee=10,
materials_fee_included_in_price=True,
instructor_percentage=0.5,
start=datetime(2024, 1, 1, 18, 0, tzinfo=timezone.get_default_timezone()),
end=datetime(2024, 1, 1, 21, 0, tzinfo=timezone.get_default_timezone()),
count=10,
category=category,
calendar=Event.EventCalendar.PURPLE,
details={
"tkt": [
{
"id": 1,
"lbl": "tkt1",
"amt": 123.4,
"cnt": 1,
"dsp": [members_folder.id],
}
],
"usr": [{"sum": 123.4}],
},
)
def test_ticket_type_annotations(self):
# TODO: test for correctness
list(EventTicketType.objects.all())
def test_with_financials(self):
# TODO: test for correctness
list(EventExt.objects.with_financials().all())
def test_with_financials_summary(self):
# TODO: test for correctness
list(
EventExt.objects.with_financials()
.values(year=TruncYear("start"))
.summarize()
.order_by("year")
)

View File

@ -15,7 +15,7 @@
"prettier": "^3.3.3",
"sass": "^1.77.8",
"typescript": "^5.5.4",
"vite": "^5.3.5"
"vite": "^5.4.0"
},
"dependencies": {
"@popperjs/core": "^2.11.8",

145
pdm.lock
View File

@ -5,7 +5,7 @@
groups = ["default", "debug", "dev", "lint", "server", "typing"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:49cf6870d89d2f0095c3c87750d4aa257d086654b700fef2e6d82b207ebfb79c"
content_hash = "sha256:9a21c503f2ee5a390ab20a4edc47ec3bf9c2899b6995600c1ba45aed79b5df65"
[[metadata.targets]]
requires_python = "==3.11.*"
@ -192,7 +192,7 @@ name = "cachetools"
version = "5.4.0"
requires_python = ">=3.7"
summary = "Extensible memoizing collections and decorators"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
files = [
{file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"},
@ -204,7 +204,7 @@ name = "certifi"
version = "2024.7.4"
requires_python = ">=3.6"
summary = "Python package for providing Mozilla's CA Bundle."
groups = ["default", "typing"]
groups = ["default", "dev", "typing"]
marker = "python_version == \"3.11\""
files = [
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
@ -231,7 +231,7 @@ name = "charset-normalizer"
version = "3.3.2"
requires_python = ">=3.7.0"
summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
groups = ["default", "typing"]
groups = ["default", "dev", "typing"]
marker = "python_version == \"3.11\""
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
@ -379,17 +379,17 @@ files = [
[[package]]
name = "django-admin-logs"
version = "1.2.0"
requires_python = ">=3.6"
version = "1.3.0"
requires_python = ">=3.8"
summary = "View, delete or disable Django admin log entries."
groups = ["default"]
marker = "python_version == \"3.11\""
dependencies = [
"Django>=3.2",
"Django>=4.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.3.0-py3-none-any.whl", hash = "sha256:e9619f185beb09397223bf973f6574db29da50c3066cc25f800f04d58af4c595"},
{file = "django_admin_logs-1.3.0.tar.gz", hash = "sha256:57cd55f2dcc04592729b2778c03b86ba94cc64f5c2e286993114ca16464ff7e9"},
]
[[package]]
@ -971,7 +971,7 @@ name = "google-api-core"
version = "2.19.1"
requires_python = ">=3.7"
summary = "Google API client core library"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"google-auth<3.0.dev0,>=2.14.1",
@ -987,10 +987,10 @@ files = [
[[package]]
name = "google-api-python-client"
version = "2.140.0"
version = "2.141.0"
requires_python = ">=3.7"
summary = "Google API Client Library for Python"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0.dev0,>=1.31.5",
@ -1000,8 +1000,25 @@ dependencies = [
"uritemplate<5,>=3.0.1",
]
files = [
{file = "google_api_python_client-2.140.0-py2.py3-none-any.whl", hash = "sha256:aeb4bb99e9fdd241473da5ff35464a0658fea0db76fe89c0f8c77ecfc3813404"},
{file = "google_api_python_client-2.140.0.tar.gz", hash = "sha256:0bb973adccbe66a3d0a70abe4e49b3f2f004d849416bfec38d22b75649d389d8"},
{file = "google_api_python_client-2.141.0-py2.py3-none-any.whl", hash = "sha256:43c05322b91791204465291b3852718fae38d4f84b411d8be847c4f86882652a"},
{file = "google_api_python_client-2.141.0.tar.gz", hash = "sha256:0f225b1f45d5a6f8c2a400f48729f5d6da9a81138e81e0478d61fdd8edf6563a"},
]
[[package]]
name = "google-api-python-client-stubs"
version = "1.26.0"
requires_python = "<4.0,>=3.7"
summary = "Type stubs for google-api-python-client"
groups = ["dev"]
marker = "python_version == \"3.11\""
dependencies = [
"google-api-python-client>=2.130.0",
"types-httplib2>=0.22.0.2",
"typing-extensions>=3.10.0",
]
files = [
{file = "google_api_python_client_stubs-1.26.0-py3-none-any.whl", hash = "sha256:0614b0cef5beac43e6ab02418f07e64ee66dc99ae4e377d54a155ac261533987"},
{file = "google_api_python_client_stubs-1.26.0.tar.gz", hash = "sha256:f3b38b46f7b5cf4f6e7cc63ca554a2d23096d49c841f38b9ea553a5237074b56"},
]
[[package]]
@ -1009,7 +1026,7 @@ name = "google-auth"
version = "2.32.0"
requires_python = ">=3.7"
summary = "Google Authentication Library"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"cachetools<6.0,>=2.0.0",
@ -1025,7 +1042,7 @@ files = [
name = "google-auth-httplib2"
version = "0.2.0"
summary = "Google Authentication Library: httplib2 transport"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"google-auth",
@ -1057,7 +1074,7 @@ name = "googleapis-common-protos"
version = "1.63.2"
requires_python = ">=3.7"
summary = "Common protobufs used in Google APIs"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"protobuf!=3.20.0,!=3.20.1,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0.dev0,>=3.20.2",
@ -1127,7 +1144,7 @@ name = "httplib2"
version = "0.22.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
summary = "A comprehensive HTTP client library."
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"pyparsing!=3.0.0,!=3.0.1,!=3.0.2,!=3.0.3,<4,>=2.4.2; python_version > \"3.0\"",
@ -1152,7 +1169,7 @@ files = [
[[package]]
name = "hypothesis"
version = "6.110.0"
version = "6.111.0"
requires_python = ">=3.8"
summary = "A library for property-based testing"
groups = ["dev"]
@ -1163,13 +1180,13 @@ dependencies = [
"sortedcontainers<3.0.0,>=2.1.0",
]
files = [
{file = "hypothesis-6.110.0-py3-none-any.whl", hash = "sha256:976200b4c0b3e315c04bf33e1bc176054dd07ff40ef2c7f4129768a50aca9753"},
{file = "hypothesis-6.110.0.tar.gz", hash = "sha256:fc1bd11cb913e83451d2cff3876ad771ab909d64b854a068d47cb4e6f53f2b0c"},
{file = "hypothesis-6.111.0-py3-none-any.whl", hash = "sha256:7a51f678da3719a04a3ef61cd241384dd93b49f35d7cce22833745c66ac1d507"},
{file = "hypothesis-6.111.0.tar.gz", hash = "sha256:04d0703621d9fdd61c079a4dda07babbe7ebf6d34eee6ad9484a2af0ee721801"},
]
[[package]]
name = "hypothesis"
version = "6.110.0"
version = "6.111.0"
extras = ["django"]
requires_python = ">=3.8"
summary = "A library for property-based testing"
@ -1177,11 +1194,11 @@ groups = ["dev"]
marker = "python_version == \"3.11\""
dependencies = [
"django>=3.2",
"hypothesis==6.110.0",
"hypothesis==6.111.0",
]
files = [
{file = "hypothesis-6.110.0-py3-none-any.whl", hash = "sha256:976200b4c0b3e315c04bf33e1bc176054dd07ff40ef2c7f4129768a50aca9753"},
{file = "hypothesis-6.110.0.tar.gz", hash = "sha256:fc1bd11cb913e83451d2cff3876ad771ab909d64b854a068d47cb4e6f53f2b0c"},
{file = "hypothesis-6.111.0-py3-none-any.whl", hash = "sha256:7a51f678da3719a04a3ef61cd241384dd93b49f35d7cce22833745c66ac1d507"},
{file = "hypothesis-6.111.0.tar.gz", hash = "sha256:04d0703621d9fdd61c079a4dda07babbe7ebf6d34eee6ad9484a2af0ee721801"},
]
[[package]]
@ -1189,7 +1206,7 @@ name = "idna"
version = "3.7"
requires_python = ">=3.5"
summary = "Internationalized Domain Names in Applications (IDNA)"
groups = ["default", "server", "typing"]
groups = ["default", "dev", "server", "typing"]
marker = "python_version == \"3.11\""
files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
@ -1264,15 +1281,15 @@ files = [
[[package]]
name = "lxml"
version = "5.2.2"
version = "5.3.0"
requires_python = ">=3.6"
summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
groups = ["default"]
marker = "python_version == \"3.11\""
files = [
{file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"},
{file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"},
{file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"},
{file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"},
{file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"},
{file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"},
]
[[package]]
@ -1584,7 +1601,7 @@ name = "proto-plus"
version = "1.24.0"
requires_python = ">=3.7"
summary = "Beautiful, Pythonic protocol buffers."
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"protobuf<6.0.0dev,>=3.19.0",
@ -1599,7 +1616,7 @@ name = "protobuf"
version = "5.27.3"
requires_python = ">=3.8"
summary = ""
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
files = [
{file = "protobuf-5.27.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:a55c48f2a2092d8e213bd143474df33a6ae751b781dd1d1f4d953c128a415b25"},
@ -1634,7 +1651,7 @@ name = "pyasn1"
version = "0.6.0"
requires_python = ">=3.8"
summary = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
files = [
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
@ -1646,7 +1663,7 @@ name = "pyasn1-modules"
version = "0.4.0"
requires_python = ">=3.8"
summary = "A collection of ASN.1-based protocols modules"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"pyasn1<0.7.0,>=0.4.6",
@ -1697,7 +1714,7 @@ name = "pyparsing"
version = "3.1.2"
requires_python = ">=3.6.8"
summary = "pyparsing module - Classes and methods to define and execute parsing grammars"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
files = [
{file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
@ -1787,7 +1804,7 @@ name = "requests"
version = "2.32.3"
requires_python = ">=3.8"
summary = "Python HTTP for Humans."
groups = ["default", "typing"]
groups = ["default", "dev", "typing"]
marker = "python_version == \"3.11\""
dependencies = [
"certifi>=2017.4.17",
@ -1821,7 +1838,7 @@ name = "rsa"
version = "4.9"
requires_python = ">=3.6,<4"
summary = "Pure-Python RSA implementation"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
dependencies = [
"pyasn1>=0.1.3",
@ -1833,14 +1850,14 @@ files = [
[[package]]
name = "ruff"
version = "0.5.6"
version = "0.6.0"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["lint"]
marker = "python_version == \"3.11\""
files = [
{file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"},
{file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"},
{file = "ruff-0.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f1749a0aef3ec41ed91a0e2127a6ae97d2e2853af16dbd4f3c00d7a3af726c5"},
{file = "ruff-0.6.0.tar.gz", hash = "sha256:272a81830f68f9bd19d49eaf7fa01a5545c5a2e86f32a9935bb0e4bb9a1db5b8"},
]
[[package]]
@ -1857,14 +1874,14 @@ files = [
[[package]]
name = "setuptools"
version = "72.1.0"
version = "72.2.0"
requires_python = ">=3.8"
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
groups = ["server", "typing"]
marker = "python_version == \"3.11\""
files = [
{file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"},
{file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"},
{file = "setuptools-72.2.0-py3-none-any.whl", hash = "sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4"},
{file = "setuptools-72.2.0.tar.gz", hash = "sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9"},
]
[[package]]
@ -2080,6 +2097,18 @@ files = [
{file = "types_html5lib-1.1.11.20240806-py3-none-any.whl", hash = "sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4"},
]
[[package]]
name = "types-httplib2"
version = "0.22.0.20240310"
requires_python = ">=3.8"
summary = "Typing stubs for httplib2"
groups = ["dev"]
marker = "python_version == \"3.11\""
files = [
{file = "types-httplib2-0.22.0.20240310.tar.gz", hash = "sha256:1eda99fea18ec8a1dc1a725ead35b889d0836fec1b11ae6f1fe05440724c1d15"},
{file = "types_httplib2-0.22.0.20240310-py3-none-any.whl", hash = "sha256:8cd706fc81f0da32789a4373a28df6f39e9d5657d1281db4d2fd22ee29e83661"},
]
[[package]]
name = "types-lxml"
version = "2024.8.7"
@ -2137,6 +2166,18 @@ files = [
{file = "types_Pygments-2.18.0.20240506-py3-none-any.whl", hash = "sha256:11c90bc1737c9af55e5569558b88df7c2233e12325cb516215f722271444e91d"},
]
[[package]]
name = "types-python-dateutil"
version = "2.9.0.20240316"
requires_python = ">=3.8"
summary = "Typing stubs for python-dateutil"
groups = ["dev"]
marker = "python_version == \"3.11\""
files = [
{file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"},
{file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"},
]
[[package]]
name = "types-pyyaml"
version = "6.0.12.20240724"
@ -2223,7 +2264,7 @@ name = "uritemplate"
version = "4.1.1"
requires_python = ">=3.6"
summary = "Implementation of RFC 6570 URI Templates"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version == \"3.11\""
files = [
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
@ -2235,7 +2276,7 @@ name = "urllib3"
version = "2.2.2"
requires_python = ">=3.8"
summary = "HTTP library with thread-safe connection pooling, file post, and more."
groups = ["default", "typing"]
groups = ["default", "dev", "typing"]
marker = "python_version == \"3.11\""
files = [
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
@ -2244,7 +2285,7 @@ files = [
[[package]]
name = "uvicorn"
version = "0.30.5"
version = "0.30.6"
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
groups = ["server"]
@ -2255,13 +2296,13 @@ dependencies = [
"typing-extensions>=4.0; python_version < \"3.11\"",
]
files = [
{file = "uvicorn-0.30.5-py3-none-any.whl", hash = "sha256:b2d86de274726e9878188fa07576c9ceeff90a839e2b6e25c917fe05f5a6c835"},
{file = "uvicorn-0.30.5.tar.gz", hash = "sha256:ac6fdbd4425c5fd17a9fe39daf4d4d075da6fdc80f653e5894cdc2fd98752bee"},
{file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"},
{file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"},
]
[[package]]
name = "uvicorn"
version = "0.30.5"
version = "0.30.6"
extras = ["standard"]
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
@ -2272,14 +2313,14 @@ dependencies = [
"httptools>=0.5.0",
"python-dotenv>=0.13",
"pyyaml>=5.1",
"uvicorn==0.30.5",
"uvicorn==0.30.6",
"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.30.5-py3-none-any.whl", hash = "sha256:b2d86de274726e9878188fa07576c9ceeff90a839e2b6e25c917fe05f5a6c835"},
{file = "uvicorn-0.30.5.tar.gz", hash = "sha256:ac6fdbd4425c5fd17a9fe39daf4d4d075da6fdc80f653e5894cdc2fd98752bee"},
{file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"},
{file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"},
]
[[package]]

View File

@ -40,8 +40,8 @@ importers:
specifier: ^5.5.4
version: 5.5.4
vite:
specifier: ^5.3.5
version: 5.3.5(sass@1.77.8)
specifier: ^5.4.0
version: 5.4.0(sass@1.77.8)
packages:
@ -344,8 +344,8 @@ packages:
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
engines: {node: '>=18'}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
immutable@4.3.7:
@ -452,8 +452,8 @@ packages:
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
engines: {node: '>=18'}
vite@5.3.5:
resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==}
vite@5.4.0:
resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@ -461,6 +461,7 @@ packages:
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
@ -473,6 +474,8 @@ packages:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
@ -705,12 +708,12 @@ snapshots:
dependencies:
'@sindresorhus/merge-streams': 2.3.0
fast-glob: 3.3.2
ignore: 5.3.1
ignore: 5.3.2
path-type: 5.0.0
slash: 5.1.0
unicorn-magic: 0.1.0
ignore@5.3.1: {}
ignore@5.3.2: {}
immutable@4.3.7: {}
@ -805,7 +808,7 @@ snapshots:
unicorn-magic@0.1.0: {}
vite@5.3.5(sass@1.77.8):
vite@5.4.0(sass@1.77.8):
dependencies:
esbuild: 0.21.5
postcss: 8.4.41

View File

@ -7,7 +7,7 @@ authors = [
]
dependencies = [
"django~=5.1",
"django-admin-logs~=1.2",
"django-admin-logs~=1.3",
"django-auth-ldap~=4.8",
"django-markdownx~=4.0",
"django-recurrence~=1.11",
@ -23,7 +23,7 @@ dependencies = [
"semver~=3.0",
"djangorestframework~=3.15",
"django-q2~=1.6",
"lxml~=5.2",
"lxml~=5.3",
"django-object-actions~=4.2",
"bitstring~=4.2",
"udm-rest-client~=1.2",
@ -41,7 +41,7 @@ dependencies = [
"django-configurations[database,email]~=2.5",
"django-vite~=3.0",
"django-template-partials~=24.2",
"google-api-python-client~=2.140",
"google-api-python-client~=2.141",
"google-auth-oauthlib~=1.2",
"django-model-utils~=4.5",
]
@ -50,7 +50,7 @@ requires-python = ">=3.11"
[project.optional-dependencies]
server = [
"uvicorn[standard]~=0.30",
"setuptools~=72.1",
"setuptools~=72.2",
]
[project.entry-points."djangoq.errorreporters"]
@ -83,6 +83,7 @@ select = [
"TCH",
"PTH",
"FURB",
"B",
]
ignore = ["ISC001"]
@ -94,6 +95,7 @@ known-first-party = [
"membershipworks",
"paperwork",
"rentals",
"reservations",
"tasks",
]
section-order = ["future", "standard-library", "django", "third-party", "first-party", "local-folder"]
@ -143,12 +145,12 @@ include_packages = ["openapi-client-udm"]
[tool.pdm.dev-dependencies]
lint = [
"djlint~=1.34",
"ruff~=0.5",
"ruff~=0.6",
]
typing = [
"mypy~=1.10",
"django-stubs~=5.0",
"setuptools~=72.1",
"setuptools~=72.2",
"types-bleach~=6.1",
"types-requests~=2.32",
"types-urllib3~=1.26",
@ -164,8 +166,10 @@ debug = [
dev = [
"django-extensions~=3.2",
"ipython~=8.26",
"hypothesis[django]~=6.110",
"hypothesis[django]~=6.111",
"tblib~=3.0",
"google-api-python-client-stubs~=1.26",
"types-python-dateutil~=2.9",
]
[tool.pdm.scripts]