Compare commits

..

No commits in common. "40eca22eb8332e4cc8b54ee5e8ea634464465126" and "b1d3f164591d25e1d19ecaa78020560bf79fa0b9" have entirely different histories.

10 changed files with 186 additions and 221 deletions

View File

@ -1,5 +1,4 @@
import os import os
import sys
from pathlib import Path from pathlib import Path
from django.core import validators from django.core import validators
@ -304,15 +303,8 @@ class Dev(NonCIBase):
DEBUG = values.BooleanValue(True) DEBUG = values.BooleanValue(True)
INTERNAL_IPS = ["127.0.0.1"] INTERNAL_IPS = ["127.0.0.1"]
INSTALLED_APPS = NonCIBase.INSTALLED_APPS + ["django_extensions"] INSTALLED_APPS = NonCIBase.INSTALLED_APPS + ["debug_toolbar", "django_extensions"]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + Base.MIDDLEWARE
# bit of a hack to disable debug toolbar when running tests
if DEBUG and "test" not in sys.argv:
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE = NonCIBase.MIDDLEWARE + [
"debug_toolbar.middleware.DebugToolbarMiddleware",
]
EMAIL = values.EmailURLValue("smtp://localhost:1025") # for local `mailpit` EMAIL = values.EmailURLValue("smtp://localhost:1025") # for local `mailpit`
SENDFILE_BACKEND = "django_sendfile.backends.development" SENDFILE_BACKEND = "django_sendfile.backends.development"

View File

@ -22,9 +22,6 @@ class Door(models.Model):
help_text="Membershipworks field that grants members access to this door", help_text="Membershipworks field that grants members access to this door",
) )
def __str__(self):
return self.name
@property @property
def controller(self) -> DoorController: def controller(self) -> DoorController:
return DoorController( return DoorController(
@ -33,6 +30,9 @@ class Door(models.Model):
settings.HID_DOOR_PASSWORD, settings.HID_DOOR_PASSWORD,
) )
def __str__(self):
return self.name
@cached_property @cached_property
def card_formats(self): def card_formats(self):
return self.controller.get_cardFormats() return self.controller.get_cardFormats()
@ -149,6 +149,8 @@ class HIDEventQuerySet(models.QuerySet):
class HIDEvent(models.Model): class HIDEvent(models.Model):
objects = HIDEventQuerySet.as_manager()
class EventType(models.IntegerChoices): class EventType(models.IntegerChoices):
DENIED_ACCESS_CARD_NOT_FOUND = 1022, "Denied Access: Card Not Found" DENIED_ACCESS_CARD_NOT_FOUND = 1022, "Denied Access: Card Not Found"
DENIED_ACCESS_ACCESS_PIN_NOT_FOUND = 1023, "Denied Access Access: PIN Not Found" DENIED_ACCESS_ACCESS_PIN_NOT_FOUND = 1023, "Denied Access Access: PIN Not Found"
@ -222,20 +224,6 @@ class HIDEvent(models.Model):
db_persist=False, db_persist=False,
) )
objects = HIDEventQuerySet.as_manager()
class Meta:
constraints = [
models.UniqueConstraint(
fields=["door", "timestamp", "event_type"], name="unique_hidevent"
)
]
db_table = "hidevent"
ordering = ("-timestamp",)
def __str__(self):
return f"{self.door.name} {self.timestamp} - {self.description}"
@classmethod @classmethod
def from_xml_attributes(cls, door: Door, attrib: dict[str, str]): def from_xml_attributes(cls, door: Door, attrib: dict[str, str]):
field_lookup = { field_lookup = {
@ -301,6 +289,9 @@ class HIDEvent(models.Model):
return event_types.get(self.event_type, f"Unknown Event Type {self.event_type}") return event_types.get(self.event_type, f"Unknown Event Type {self.event_type}")
def __str__(self):
return f"{self.door.name} {self.timestamp} - {self.description}"
def decoded_card_number(self) -> str | None: def decoded_card_number(self) -> str | None:
"""Requires annotations from `with_decoded_card_number`""" """Requires annotations from `with_decoded_card_number`"""
if self.raw_card_number is None: if self.raw_card_number is None:
@ -312,3 +303,12 @@ class HIDEvent(models.Model):
return "Invalid" return "Invalid"
else: else:
return "Not 26 bit card" return "Not 26 bit card"
class Meta:
constraints = [
models.UniqueConstraint(
fields=["door", "timestamp", "event_type"], name="unique_hidevent"
)
]
db_table = "hidevent"
ordering = ("-timestamp",)

View File

@ -1,10 +1,7 @@
import dataclasses import dataclasses
import logging import logging
from datetime import timedelta
from typing import TypedDict from typing import TypedDict
from django.utils import timezone
from django_q.tasks import async_task from django_q.tasks import async_task
from cmsmanage.django_q2_helper import q_task_group from cmsmanage.django_q2_helper import q_task_group
@ -65,17 +62,6 @@ class DoorMember:
if getattr(member, attribute_rule.access_field) if getattr(member, attribute_rule.access_field)
} }
# grant instructors access for ~1 hour around their class times
if hasattr(member, "eventinstructor") and getattr(member, door.access_field):
now = timezone.now()
margin = timedelta(hours=1)
if member.eventinstructor.eventext_set.filter(
occurred=True,
meeting_times__start__lt=now + margin,
meeting_times__end__gt=now - margin,
).exists():
reasons_and_schedules["Instructor for Active Class"] = "Active Class"
reasons = sorted(reasons_and_schedules.keys()) reasons = sorted(reasons_and_schedules.keys())
return cls( return cls(

View File

@ -1,18 +1,8 @@
import logging
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from membershipworks.tasks.scrape import logger, scrape_events from membershipworks.tasks.scrape import scrape_events
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, verbosity: int, **options): def handle(self, *args, **options):
verbosity_levels = {
0: logging.ERROR,
1: logging.WARNING,
2: logging.INFO,
3: logging.DEBUG,
}
logger.setLevel(verbosity_levels.get(verbosity, logging.WARNING))
scrape_events() scrape_events()

View File

@ -88,13 +88,13 @@ class Flag(BaseModel):
name = models.TextField(null=True, blank=True) name = models.TextField(null=True, blank=True)
type = models.CharField(max_length=6) type = models.CharField(max_length=6)
def __str__(self):
return f"{self.name} ({self.type})"
class Meta: class Meta:
db_table = "flag" db_table = "flag"
ordering = ("name",) ordering = ("name",)
def __str__(self):
return f"{self.name} ({self.type})"
class MemberQuerySet(models.QuerySet): class MemberQuerySet(models.QuerySet):
# TODO: maybe rename to reflect EXISTS? # TODO: maybe rename to reflect EXISTS?
@ -122,6 +122,8 @@ class MemberQuerySet(models.QuerySet):
# TODO: is this still a temporal table? # TODO: is this still a temporal table?
class Member(BaseModel): class Member(BaseModel):
objects = MemberQuerySet.as_manager()
uid = models.CharField(max_length=24, primary_key=True) uid = models.CharField(max_length=24, primary_key=True)
year_of_birth = models.TextField(db_column="Year of Birth", null=True, blank=True) year_of_birth = models.TextField(db_column="Year of Birth", null=True, blank=True)
account_name = models.TextField(db_column="Account Name", null=True, blank=True) account_name = models.TextField(db_column="Account Name", null=True, blank=True)
@ -262,7 +264,8 @@ class Member(BaseModel):
"Waiver form signed and on file date.": "%m/%d/%Y", "Waiver form signed and on file date.": "%m/%d/%Y",
} }
objects = MemberQuerySet.as_manager() def __str__(self):
return f"{self.account_name}"
class Meta: class Meta:
db_table = "members" db_table = "members"
@ -273,9 +276,6 @@ class Member(BaseModel):
models.Index(fields=["last_name"], name="last_name_idx"), models.Index(fields=["last_name"], name="last_name_idx"),
] ]
def __str__(self):
return f"{self.account_name}"
@classmethod @classmethod
def from_user(cls, user) -> "Member | None": def from_user(cls, user) -> "Member | None":
if hasattr(user, "ldap_user"): if hasattr(user, "ldap_user"):
@ -304,6 +304,9 @@ class MemberFlag(BaseModel):
) )
flag = models.ForeignKey(Flag, on_delete=models.PROTECT) flag = models.ForeignKey(Flag, on_delete=models.PROTECT)
def __str__(self):
return f"{self.member} - {self.flag}"
class Meta: class Meta:
db_table = "memberflag" db_table = "memberflag"
constraints = [ constraints = [
@ -312,9 +315,6 @@ class MemberFlag(BaseModel):
) )
] ]
def __str__(self):
return f"{self.member} - {self.flag}"
class Transaction(BaseModel): class Transaction(BaseModel):
sid = models.CharField(max_length=256, null=True, blank=True) sid = models.CharField(max_length=256, null=True, blank=True)
@ -364,24 +364,24 @@ class Transaction(BaseModel):
"_dp": None, "_dp": None,
} }
class Meta:
db_table = "transactions"
def __str__(self): def __str__(self):
return f"{self.type} [{self.member if self.member else self.name}] {self.timestamp}" return f"{self.type} [{self.member if self.member else self.name}] {self.timestamp}"
class Meta:
db_table = "transactions"
class EventCategory(models.Model): class EventCategory(models.Model):
id = models.IntegerField(primary_key=True) id = models.IntegerField(primary_key=True)
title = models.TextField() title = models.TextField()
def __str__(self):
return self.title
@classmethod @classmethod
def from_api_dict(cls, id: int, data): def from_api_dict(cls, id: int, data):
return cls(id=id, title=data["ttl"]) return cls(id=id, title=data["ttl"])
def __str__(self):
return self.title
class Event(BaseModel): class Event(BaseModel):
class EventCalendar(models.IntegerChoices): class EventCalendar(models.IntegerChoices):
@ -433,13 +433,13 @@ class Event(BaseModel):
_allowed_missing_fields = ["cap", "edp", "adn"] _allowed_missing_fields = ["cap", "edp", "adn"]
def __str__(self):
return self.unescaped_title
@property @property
def unescaped_title(self): def unescaped_title(self):
return nh3.clean(self.title, tags=set()) return nh3.clean(self.title, tags=set())
def __str__(self):
return self.unescaped_title
class EventInstructor(models.Model): class EventInstructor(models.Model):
name = models.TextField(blank=True) name = models.TextField(blank=True)
@ -526,6 +526,8 @@ class EventExtManager(models.Manager):
class EventExt(Event): class EventExt(Event):
"""Extension of `Event` to capture some fields not supported in MembershipWorks""" """Extension of `Event` to capture some fields not supported in MembershipWorks"""
objects = EventExtManager.from_queryset(EventExtQuerySet)()
instructor = models.ForeignKey( instructor = models.ForeignKey(
EventInstructor, on_delete=models.PROTECT, null=True, blank=True EventInstructor, on_delete=models.PROTECT, null=True, blank=True
) )
@ -555,12 +557,6 @@ class EventExt(Event):
should_survey = models.BooleanField(default=False) should_survey = models.BooleanField(default=False)
survey_email_sent = models.BooleanField(default=False) survey_email_sent = models.BooleanField(default=False)
objects = EventExtManager.from_queryset(EventExtQuerySet)()
class Meta:
verbose_name = "event"
ordering = ["-start"]
def get_absolute_url(self) -> str: def get_absolute_url(self) -> str:
return reverse("membershipworks:event-detail", kwargs={"eid": self.eid}) return reverse("membershipworks:event-detail", kwargs={"eid": self.eid})
@ -585,6 +581,10 @@ class EventExt(Event):
and getattr(self, "total_due_to_instructor") is not None and getattr(self, "total_due_to_instructor") is not None
) )
class Meta:
verbose_name = "event"
ordering = ["-start"]
if TYPE_CHECKING: if TYPE_CHECKING:
@ -633,9 +633,6 @@ class EventMeetingTime(models.Model):
models.CheckConstraint(check=Q(end__gt=F("start")), name="end_after_start"), models.CheckConstraint(check=Q(end__gt=F("start")), name="end_after_start"),
] ]
def __str__(self) -> str:
return f"{self.start} - {self.end}"
class EventInvoice(models.Model): class EventInvoice(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

View File

@ -20,14 +20,14 @@ class AbstractAudit(models.Model):
good = models.BooleanField(default=False) good = models.BooleanField(default=False)
notes = models.CharField(max_length=255, blank=True) notes = models.CharField(max_length=255, blank=True)
def __str__(self) -> str:
return f"{'Good' if self.good else 'Bad'} audit at {self.date} by {self.user}"
class Meta: class Meta:
abstract = True abstract = True
ordering = ["date"] ordering = ["date"]
get_latest_by = ["date"] get_latest_by = ["date"]
def __str__(self) -> str:
return f"{'Good' if self.good else 'Bad'} audit at {self.date} by {self.user}"
class CmsRedRiverVeteransScholarship(models.Model): class CmsRedRiverVeteransScholarship(models.Model):
serial = models.AutoField(primary_key=True) serial = models.AutoField(primary_key=True)
@ -61,12 +61,12 @@ class CmsRedRiverVeteransScholarship(models.Model):
db_column="Program Status", max_length=16, blank=True, null=True db_column="Program Status", max_length=16, blank=True, null=True
) )
class Meta:
db_table = "CMS Red River Veterans Scholarship"
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.program_name} {self.member_name}" return f"{self.program_name} {self.member_name}"
class Meta:
db_table = "CMS Red River Veterans Scholarship"
class DepartmentQuerySet(models.QuerySet): class DepartmentQuerySet(models.QuerySet):
def filter_by_shop_lead(self, member: Member) -> models.QuerySet["Department"]: def filter_by_shop_lead(self, member: Member) -> models.QuerySet["Department"]:
@ -80,6 +80,8 @@ class DepartmentQuerySet(models.QuerySet):
class Department(models.Model): class Department(models.Model):
objects = DepartmentQuerySet.as_manager()
name = models.CharField( name = models.CharField(
max_length=64, max_length=64,
validators=[RegexValidator("^[-_ A-Za-z0-9]*$")], validators=[RegexValidator("^[-_ A-Za-z0-9]*$")],
@ -99,8 +101,6 @@ class Department(models.Model):
) )
list_reply_to_address = models.EmailField(max_length=254, blank=True) list_reply_to_address = models.EmailField(max_length=254, blank=True)
objects = DepartmentQuerySet.as_manager()
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
@ -123,13 +123,13 @@ class CertificationDefinition(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
department = models.ForeignKey(Department, models.PROTECT) department = models.ForeignKey(Department, models.PROTECT)
def __str__(self) -> str:
return f"{self.name} <{self.department}>"
class Meta: class Meta:
db_table = "Certification Definitions" db_table = "Certification Definitions"
ordering = ("name", "department") ordering = ("name", "department")
def __str__(self) -> str:
return f"{self.name} <{self.department}>"
def latest_version(self) -> "CertificationVersion": def latest_version(self) -> "CertificationVersion":
return self.certificationversion_set.latest() return self.certificationversion_set.latest()
@ -157,6 +157,8 @@ class CertificationVersionManager(models.Manager["CertificationVersion"]):
class CertificationVersion(models.Model): class CertificationVersion(models.Model):
objects = CertificationVersionManager()
definition = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT) definition = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT)
major = models.PositiveSmallIntegerField() major = models.PositiveSmallIntegerField()
minor = models.PositiveSmallIntegerField() minor = models.PositiveSmallIntegerField()
@ -164,7 +166,8 @@ class CertificationVersion(models.Model):
prerelease = models.CharField(max_length=255, blank=True) prerelease = models.CharField(max_length=255, blank=True)
approval_date = models.DateField(blank=True, null=True) approval_date = models.DateField(blank=True, null=True)
objects = CertificationVersionManager() def __str__(self) -> str:
return f"{self.definition} [{self.semantic_version()}]"
class Meta: class Meta:
constraints = [ constraints = [
@ -184,9 +187,6 @@ class CertificationVersion(models.Model):
get_latest_by = ("major", "minor", "patch", "prerelease", "approval_date") get_latest_by = ("major", "minor", "patch", "prerelease", "approval_date")
base_manager_name = "objects" base_manager_name = "objects"
def __str__(self) -> str:
return f"{self.definition} [{self.semantic_version()}]"
def semantic_version(self) -> VersionInfo: def semantic_version(self) -> VersionInfo:
return VersionInfo( return VersionInfo(
self.major or 0, self.major or 0,
@ -224,6 +224,9 @@ class Certification(models.Model):
shop_lead_notified = models.DateTimeField(blank=True, null=True) shop_lead_notified = models.DateTimeField(blank=True, null=True)
notes = models.CharField(max_length=255, blank=True, null=True) notes = models.CharField(max_length=255, blank=True, null=True)
def __str__(self) -> str:
return f"{self.name} - {self.certification_version}"
class Meta: class Meta:
db_table = "Certifications" db_table = "Certifications"
permissions = [ permissions = [
@ -233,9 +236,6 @@ class Certification(models.Model):
), ),
] ]
def __str__(self) -> str:
return f"{self.name} - {self.certification_version}"
class CertificationAudit(AbstractAudit): class CertificationAudit(AbstractAudit):
certification = models.ForeignKey( certification = models.ForeignKey(
@ -255,12 +255,12 @@ class InstructorOrVendor(models.Model):
db_column="email address", max_length=255, blank=True, null=True db_column="email address", max_length=255, blank=True, null=True
) )
class Meta:
db_table = "Instructors and Vendors"
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.name}" return f"{self.name}"
class Meta:
db_table = "Instructors and Vendors"
class SpecialProgram(models.Model): class SpecialProgram(models.Model):
program_name = models.CharField( program_name = models.CharField(
@ -292,12 +292,12 @@ class SpecialProgram(models.Model):
db_column="Program Status", max_length=16, blank=True, null=True db_column="Program Status", max_length=16, blank=True, null=True
) )
class Meta:
db_table = "Special_Programs"
def __str__(self) -> str: def __str__(self) -> str:
return self.program_name return self.program_name
class Meta:
db_table = "Special_Programs"
class Waiver(models.Model): class Waiver(models.Model):
number = models.AutoField(db_column="Number", primary_key=True) number = models.AutoField(db_column="Number", primary_key=True)
@ -319,12 +319,12 @@ class Waiver(models.Model):
guardian_date = models.DateField(db_column="Guardian Date", blank=True, null=True) guardian_date = models.DateField(db_column="Guardian Date", blank=True, null=True)
notes = models.CharField(max_length=255, blank=True, null=True) notes = models.CharField(max_length=255, blank=True, null=True)
class Meta:
db_table = "Waivers"
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.name} {self.date}" return f"{self.name} {self.date}"
class Meta:
db_table = "Waivers"
class WaiverAudit(AbstractAudit): class WaiverAudit(AbstractAudit):
waiver = models.ForeignKey(Waiver, on_delete=models.CASCADE, related_name="audits") waiver = models.ForeignKey(Waiver, on_delete=models.CASCADE, related_name="audits")

144
pdm.lock
View File

@ -5,7 +5,7 @@
groups = ["default", "debug", "lint", "server", "typing", "dev"] groups = ["default", "debug", "lint", "server", "typing", "dev"]
strategy = ["cross_platform"] strategy = ["cross_platform"]
lock_version = "4.4.1" lock_version = "4.4.1"
content_hash = "sha256:b32d3518b2ef3d797f260f44adff417959def44b75fb60134fcdd93351c92581" content_hash = "sha256:8b3bb37d6fbada119262035c0e94ada756c2f43ca8863b50b51f47a232ee84b9"
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
@ -224,15 +224,15 @@ files = [
[[package]] [[package]]
name = "bitstring" name = "bitstring"
version = "4.2.3" version = "4.2.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Simple construction, analysis and modification of binary data." summary = "Simple construction, analysis and modification of binary data."
dependencies = [ dependencies = [
"bitarray<3.0.0,>=2.9.0", "bitarray<3.0.0,>=2.9.0",
] ]
files = [ files = [
{file = "bitstring-4.2.3-py3-none-any.whl", hash = "sha256:20ed0036e2fcf0323acb0f92f0b7b178516a080f3e91061470aa019ac4ede404"}, {file = "bitstring-4.2.2-py3-none-any.whl", hash = "sha256:8b784373e78e953879c8192589e1ecbcda8f111841633a2aaf88d2b073fd6c35"},
{file = "bitstring-4.2.3.tar.gz", hash = "sha256:e0c447af3fda0d114f77b88c2d199f02f97ee7e957e6d719f40f41cf15fbb897"}, {file = "bitstring-4.2.2.tar.gz", hash = "sha256:b40b01d911eebaea6efff40d826580806dced5e04b9d3cbad6aebf9422f4b643"},
] ]
[[package]] [[package]]
@ -578,29 +578,29 @@ files = [
[[package]] [[package]]
name = "django-db-views" name = "django-db-views"
version = "0.1.8" version = "0.1.7"
summary = "Handle database views. Allow to create migrations for database views. View migrations using django code. They can be reversed. Changes in model view definition are detected automatically. Support almost all options as regular makemigrations command" summary = "Handle database views. Allow to create migrations for database views. View migrations using django code. They can be reversed. Changes in model view definition are detected automatically. Support almost all options as regular makemigrations command"
dependencies = [ dependencies = [
"django>=2.2", "Django",
"six", "six",
] ]
files = [ files = [
{file = "django_db_views-0.1.8-py2.py3-none-any.whl", hash = "sha256:089c9f193c265f956c0f06d0695b54ff57343bbb25f6c5d546f816e7cb9b456c"}, {file = "django-db-views-0.1.7.tar.gz", hash = "sha256:7c0dc78aa5f53cc4eefc4d880450a8cb61bb8376b5494356e20383f71a3e1657"},
{file = "django_db_views-0.1.8.tar.gz", hash = "sha256:b737b01b782d859a90f4d579147a5fa34f4b5376606443abe35c5fdc00313bcc"}, {file = "django_db_views-0.1.7-py3-none-any.whl", hash = "sha256:ffa399af1678e60f532f8c2b531927e94e8249e1012a83fc865234384419bff8"},
] ]
[[package]] [[package]]
name = "django-debug-toolbar" name = "django-debug-toolbar"
version = "4.4.2" version = "4.3.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "A configurable set of panels that display various debug information about the current request/response." summary = "A configurable set of panels that display various debug information about the current request/response."
dependencies = [ dependencies = [
"django>=4.2.9", "django>=3.2.4",
"sqlparse>=0.2", "sqlparse>=0.2",
] ]
files = [ files = [
{file = "django_debug_toolbar-4.4.2-py3-none-any.whl", hash = "sha256:5d7afb2ea5f8730241e5b0735396e16cd1fd8c6b53a2f3e1e30bbab9abb23728"}, {file = "django_debug_toolbar-4.3.0-py3-none-any.whl", hash = "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6"},
{file = "django_debug_toolbar-4.4.2.tar.gz", hash = "sha256:9204050fcb1e4f74216c5b024bc76081451926a6303993d6c513f5e142675927"}, {file = "django_debug_toolbar-4.3.0.tar.gz", hash = "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4"},
] ]
[[package]] [[package]]
@ -734,24 +734,24 @@ files = [
[[package]] [[package]]
name = "django-stubs" name = "django-stubs"
version = "5.0.2" version = "5.0.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Mypy stubs for Django" summary = "Mypy stubs for Django"
dependencies = [ dependencies = [
"asgiref", "asgiref",
"django", "django",
"django-stubs-ext>=5.0.2", "django-stubs-ext>=5.0.0",
"types-PyYAML", "types-PyYAML",
"typing-extensions>=4.11.0", "typing-extensions",
] ]
files = [ files = [
{file = "django_stubs-5.0.2-py3-none-any.whl", hash = "sha256:cb0c506cb5c54c64612e4a2ee8d6b913c6178560ec168009fe847c09747c304b"}, {file = "django_stubs-5.0.0-py3-none-any.whl", hash = "sha256:084484cbe16a6d388e80ec687e46f529d67a232f3befaf55c936b3b476be289d"},
{file = "django_stubs-5.0.2.tar.gz", hash = "sha256:236bc5606e5607cb968f92b648471f9edaa461a774bc013bf9e6bff8730f6bdf"}, {file = "django_stubs-5.0.0.tar.gz", hash = "sha256:b8a792bee526d6cab31e197cb414ee7fa218abd931a50948c66a80b3a2548621"},
] ]
[[package]] [[package]]
name = "django-stubs-ext" name = "django-stubs-ext"
version = "5.0.2" version = "5.0.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Monkey-patching and extensions for django-stubs" summary = "Monkey-patching and extensions for django-stubs"
dependencies = [ dependencies = [
@ -759,23 +759,23 @@ dependencies = [
"typing-extensions", "typing-extensions",
] ]
files = [ files = [
{file = "django_stubs_ext-5.0.2-py3-none-any.whl", hash = "sha256:8d8efec5a86241266bec94a528fe21258ad90d78c67307f3ae5f36e81de97f12"}, {file = "django_stubs_ext-5.0.0-py3-none-any.whl", hash = "sha256:8e1334fdf0c8bff87e25d593b33d4247487338aaed943037826244ff788b56a8"},
{file = "django_stubs_ext-5.0.2.tar.gz", hash = "sha256:409c62585d7f996cef5c760e6e27ea3ff29f961c943747e67519c837422cad32"}, {file = "django_stubs_ext-5.0.0.tar.gz", hash = "sha256:5bacfbb498a206d5938454222b843d81da79ea8b6fcd1a59003f529e775bc115"},
] ]
[[package]] [[package]]
name = "django-stubs" name = "django-stubs"
version = "5.0.2" version = "5.0.0"
extras = ["compatible-mypy"] extras = ["compatible-mypy"]
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Mypy stubs for Django" summary = "Mypy stubs for Django"
dependencies = [ dependencies = [
"django-stubs==5.0.2", "django-stubs==5.0.0",
"mypy~=1.10.0", "mypy~=1.10.0",
] ]
files = [ files = [
{file = "django_stubs-5.0.2-py3-none-any.whl", hash = "sha256:cb0c506cb5c54c64612e4a2ee8d6b913c6178560ec168009fe847c09747c304b"}, {file = "django_stubs-5.0.0-py3-none-any.whl", hash = "sha256:084484cbe16a6d388e80ec687e46f529d67a232f3befaf55c936b3b476be289d"},
{file = "django_stubs-5.0.2.tar.gz", hash = "sha256:236bc5606e5607cb968f92b648471f9edaa461a774bc013bf9e6bff8730f6bdf"}, {file = "django_stubs-5.0.0.tar.gz", hash = "sha256:b8a792bee526d6cab31e197cb414ee7fa218abd931a50948c66a80b3a2548621"},
] ]
[[package]] [[package]]
@ -1079,7 +1079,7 @@ files = [
[[package]] [[package]]
name = "hypothesis" name = "hypothesis"
version = "6.103.1" version = "6.102.4"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "A library for property-based testing" summary = "A library for property-based testing"
dependencies = [ dependencies = [
@ -1087,23 +1087,23 @@ dependencies = [
"sortedcontainers<3.0.0,>=2.1.0", "sortedcontainers<3.0.0,>=2.1.0",
] ]
files = [ files = [
{file = "hypothesis-6.103.1-py3-none-any.whl", hash = "sha256:d3c959fab6233e78867499e2117ae9db8dc40eeed936d71a2cfc7b6094972e74"}, {file = "hypothesis-6.102.4-py3-none-any.whl", hash = "sha256:013df31b04a4daede13756f497e60e451963d86f426395a79f99c5d692919bbd"},
{file = "hypothesis-6.103.1.tar.gz", hash = "sha256:d299d5c21d6408eab3be670c94c974f3acf0b511c61fe81804b09091e393ee1f"}, {file = "hypothesis-6.102.4.tar.gz", hash = "sha256:59b4d144346d5cffb482cc1bafbd21b13ff31608e8c4b3e4630339aee3e87763"},
] ]
[[package]] [[package]]
name = "hypothesis" name = "hypothesis"
version = "6.103.1" version = "6.102.4"
extras = ["django"] extras = ["django"]
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "A library for property-based testing" summary = "A library for property-based testing"
dependencies = [ dependencies = [
"django>=3.2", "django>=3.2",
"hypothesis==6.103.1", "hypothesis==6.102.4",
] ]
files = [ files = [
{file = "hypothesis-6.103.1-py3-none-any.whl", hash = "sha256:d3c959fab6233e78867499e2117ae9db8dc40eeed936d71a2cfc7b6094972e74"}, {file = "hypothesis-6.102.4-py3-none-any.whl", hash = "sha256:013df31b04a4daede13756f497e60e451963d86f426395a79f99c5d692919bbd"},
{file = "hypothesis-6.103.1.tar.gz", hash = "sha256:d299d5c21d6408eab3be670c94c974f3acf0b511c61fe81804b09091e393ee1f"}, {file = "hypothesis-6.102.4.tar.gz", hash = "sha256:59b4d144346d5cffb482cc1bafbd21b13ff31608e8c4b3e4630339aee3e87763"},
] ]
[[package]] [[package]]
@ -1118,7 +1118,7 @@ files = [
[[package]] [[package]]
name = "ipython" name = "ipython"
version = "8.25.0" version = "8.24.0"
requires_python = ">=3.10" requires_python = ">=3.10"
summary = "IPython: Productive Interactive Computing" summary = "IPython: Productive Interactive Computing"
dependencies = [ dependencies = [
@ -1134,8 +1134,8 @@ dependencies = [
"typing-extensions>=4.6; python_version < \"3.12\"", "typing-extensions>=4.6; python_version < \"3.12\"",
] ]
files = [ files = [
{file = "ipython-8.25.0-py3-none-any.whl", hash = "sha256:53eee7ad44df903a06655871cbab66d156a051fd86f3ec6750470ac9604ac1ab"}, {file = "ipython-8.24.0-py3-none-any.whl", hash = "sha256:d7bf2f6c4314984e3e02393213bab8703cf163ede39672ce5918c51fe253a2a3"},
{file = "ipython-8.25.0.tar.gz", hash = "sha256:c6ed726a140b6e725b911528f80439c534fac915246af3efc39440a6b0f9d716"}, {file = "ipython-8.24.0.tar.gz", hash = "sha256:010db3f8a728a578bb641fdd06c063b9fb8e96a9464c63aec6310fbcb5e80501"},
] ]
[[package]] [[package]]
@ -1817,8 +1817,8 @@ files = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.3" version = "2.31.0"
requires_python = ">=3.8" requires_python = ">=3.7"
summary = "Python HTTP for Humans." summary = "Python HTTP for Humans."
dependencies = [ dependencies = [
"certifi>=2017.4.17", "certifi>=2017.4.17",
@ -1827,33 +1827,33 @@ dependencies = [
"urllib3<3,>=1.21.1", "urllib3<3,>=1.21.1",
] ]
files = [ files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
] ]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.4.8" version = "0.4.4"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust." summary = "An extremely fast Python linter and code formatter, written in Rust."
files = [ files = [
{file = "ruff-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7663a6d78f6adb0eab270fa9cf1ff2d28618ca3a652b60f2a234d92b9ec89066"}, {file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"},
{file = "ruff-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eeceb78da8afb6de0ddada93112869852d04f1cd0f6b80fe464fd4e35c330913"}, {file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aad360893e92486662ef3be0a339c5ca3c1b109e0134fcd37d534d4be9fb8de3"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:284c2e3f3396fb05f5f803c9fffb53ebbe09a3ebe7dda2929ed8d73ded736deb"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7354f921e3fbe04d2a62d46707e569f9315e1a613307f7311a935743c51a764"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:72584676164e15a68a15778fd1b17c28a519e7a0622161eb2debdcdabdc71883"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9678d5c9b43315f323af2233a04d747409d1e3aa6789620083a82d1066a35199"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704977a658131651a22b5ebeb28b717ef42ac6ee3b11e91dc87b633b5d83142b"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05f8d6f0c3cce5026cecd83b7a143dcad503045857bc49662f736437380ad45"}, {file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ea874950daca5697309d976c9afba830d3bf0ed66887481d6bca1673fc5b66a"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fc95aac2943ddf360376be9aa3107c8cf9640083940a8c5bd824be692d2216dc"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:384154a1c3f4bf537bac69f33720957ee49ac8d484bfc91720cc94172026ceed"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e9d5ce97cacc99878aa0d084c626a15cd21e6b3d53fd6f9112b7fc485918e1fa"}, {file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"},
{file = "ruff-0.4.8-py3-none-win32.whl", hash = "sha256:6d795d7639212c2dfd01991259460101c22aabf420d9b943f153ab9d9706e6a9"}, {file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"},
{file = "ruff-0.4.8-py3-none-win_amd64.whl", hash = "sha256:e14a3a095d07560a9d6769a72f781d73259655919d9b396c650fc98a8157555d"}, {file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"},
{file = "ruff-0.4.8-py3-none-win_arm64.whl", hash = "sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780"}, {file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"},
{file = "ruff-0.4.8.tar.gz", hash = "sha256:16d717b1d57b2e2fd68bd0bf80fb43931b79d05a7131aa477d66fc40fbd86268"}, {file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"},
] ]
[[package]] [[package]]
@ -1868,12 +1868,12 @@ files = [
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "70.0.0" version = "69.5.1"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Easily download, build, install, upgrade, and uninstall Python packages" summary = "Easily download, build, install, upgrade, and uninstall Python packages"
files = [ files = [
{file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
{file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"},
] ]
[[package]] [[package]]
@ -2117,15 +2117,15 @@ files = [
[[package]] [[package]]
name = "types-requests" name = "types-requests"
version = "2.32.0.20240602" version = "2.31.0.20240406"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Typing stubs for requests" summary = "Typing stubs for requests"
dependencies = [ dependencies = [
"urllib3>=2", "urllib3>=2",
] ]
files = [ files = [
{file = "types-requests-2.32.0.20240602.tar.gz", hash = "sha256:3f98d7bbd0dd94ebd10ff43a7fbe20c3b8528acace6d8efafef0b6a184793f06"}, {file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"},
{file = "types_requests-2.32.0.20240602-py3-none-any.whl", hash = "sha256:ed3946063ea9fbc6b5fc0c44fa279188bae42d582cb63760be6cb4b9d06c3de8"}, {file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"},
] ]
[[package]] [[package]]
@ -2196,7 +2196,7 @@ files = [
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.30.1" version = "0.29.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "The lightning-fast ASGI server." summary = "The lightning-fast ASGI server."
dependencies = [ dependencies = [
@ -2204,13 +2204,13 @@ dependencies = [
"h11>=0.8", "h11>=0.8",
] ]
files = [ files = [
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"}, {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
] ]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.30.1" version = "0.29.0"
extras = ["standard"] extras = ["standard"]
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "The lightning-fast ASGI server." summary = "The lightning-fast ASGI server."
@ -2219,14 +2219,14 @@ dependencies = [
"httptools>=0.5.0", "httptools>=0.5.0",
"python-dotenv>=0.13", "python-dotenv>=0.13",
"pyyaml>=5.1", "pyyaml>=5.1",
"uvicorn==0.30.1", "uvicorn==0.29.0",
"uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"", "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", "watchfiles>=0.13",
"websockets>=10.4", "websockets>=10.4",
] ]
files = [ files = [
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"}, {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
] ]
[[package]] [[package]]
@ -2311,7 +2311,7 @@ files = [
[[package]] [[package]]
name = "weasyprint" name = "weasyprint"
version = "62.2" version = "62.1"
requires_python = ">=3.9" requires_python = ">=3.9"
summary = "The Awesome Document Factory" summary = "The Awesome Document Factory"
dependencies = [ dependencies = [
@ -2325,8 +2325,8 @@ dependencies = [
"tinycss2>=1.3.0", "tinycss2>=1.3.0",
] ]
files = [ files = [
{file = "weasyprint-62.2-py3-none-any.whl", hash = "sha256:6fd84e9f55ac239c5657845eae117fd43916c3c5986fe98f69ea13fdab8ec9ad"}, {file = "weasyprint-62.1-py3-none-any.whl", hash = "sha256:654d4c266336cbf9acc4da118c7778ef5839717e6055d5b8f995cf50be200c46"},
{file = "weasyprint-62.2.tar.gz", hash = "sha256:a08ac400e11919d996d76becaa33160d7c1ac55ba160628c42ce7586574c1a51"}, {file = "weasyprint-62.1.tar.gz", hash = "sha256:bf3c1a9ac4194271a7cf117229c093744105b50ac2fa64c0a6e44e68ae742992"},
] ]
[[package]] [[package]]

View File

@ -18,8 +18,8 @@ dependencies = [
"mdformat-tables~=0.4", "mdformat-tables~=0.4",
"mysqlclient~=2.2", "mysqlclient~=2.2",
"django-autocomplete-light~=3.11", "django-autocomplete-light~=3.11",
"weasyprint~=62.2", "weasyprint~=62.1",
"requests~=2.32", "requests~=2.31",
"semver~=3.0", "semver~=3.0",
"djangorestframework~=3.15", "djangorestframework~=3.15",
"django-q2~=1.6", "django-q2~=1.6",
@ -44,8 +44,8 @@ requires-python = ">=3.11"
[project.optional-dependencies] [project.optional-dependencies]
server = [ server = [
"uvicorn[standard]~=0.30", "uvicorn[standard]~=0.29",
"setuptools~=70.0", "setuptools~=69.5",
] ]
[project.entry-points."djangoq.errorreporters"] [project.entry-points."djangoq.errorreporters"]
@ -55,7 +55,7 @@ admin_email = "cmsmanage.django_q2_admin_email_reporter:AdminEmailReporter"
line-length = 88 line-length = 88
[tool.ruff.lint] [tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "C4", "UP", "PERF", "PL", "SIM", "FIX003", "DJ012"] select = ["E4", "E7", "E9", "F", "I", "C4", "UP", "PERF", "PL", "SIM", "FIX003"]
[tool.ruff.lint.isort] [tool.ruff.lint.isort]
known-first-party = [ known-first-party = [
@ -118,9 +118,9 @@ lint = [
typing = [ typing = [
"mypy~=1.10", "mypy~=1.10",
"django-stubs~=5.0", "django-stubs~=5.0",
"setuptools~=70.0", "setuptools~=69.5",
"types-bleach~=6.1", "types-bleach~=6.1",
"types-requests~=2.32", "types-requests~=2.31",
"types-urllib3~=1.26", "types-urllib3~=1.26",
"djangorestframework-stubs[compatible-mypy]~=3.15", "djangorestframework-stubs[compatible-mypy]~=3.15",
"types-Markdown~=3.6", "types-Markdown~=3.6",
@ -129,12 +129,12 @@ typing = [
"types-lxml~=2024.4", "types-lxml~=2024.4",
] ]
debug = [ debug = [
"django-debug-toolbar~=4.4", "django-debug-toolbar~=4.3",
] ]
dev = [ dev = [
"django-extensions~=3.2", "django-extensions~=3.2",
"ipython~=8.25", "ipython~=8.24",
"hypothesis[django]~=6.103", "hypothesis[django]~=6.102",
"tblib~=3.0", "tblib~=3.0",
] ]

View File

@ -33,6 +33,17 @@ class LockerUnit(models.Model):
rows = models.PositiveIntegerField(default=5) rows = models.PositiveIntegerField(default=5)
columns = models.PositiveIntegerField(default=2) columns = models.PositiveIntegerField(default=2)
def save(self, *args, **kwargs):
if self._state.adding:
# Create LockerInfo for each locker
with transaction.atomic():
super().save(self, *args, **kwargs)
for column in range(self.columns):
for row in range(self.rows):
self.lockers.create(column=column + 1, row=row + 1)
else:
super().save(self, *args, **kwargs)
class Meta: class Meta:
# TODO: add constraint to check for letter overlaps # TODO: add constraint to check for letter overlaps
constraints = [ constraints = [
@ -45,17 +56,6 @@ class LockerUnit(models.Model):
last_number = self.first_number + self.columns * self.rows last_number = self.first_number + self.columns * self.rows
return f"{self.bank.name} (Unit {last_letter}{self.first_number}-{self.first_letter}{last_number})" return f"{self.bank.name} (Unit {last_letter}{self.first_number}-{self.first_letter}{last_number})"
def save(self, *args, **kwargs):
if self._state.adding:
# Create LockerInfo for each locker
with transaction.atomic():
super().save(self, *args, **kwargs)
for column in range(self.columns):
for row in range(self.rows):
self.lockers.create(column=column + 1, row=row + 1)
else:
super().save(self, *args, **kwargs)
def letter_for_column(self, column: int) -> str: def letter_for_column(self, column: int) -> str:
return chr(column + ord(self.first_letter)) return chr(column + ord(self.first_letter))
@ -91,6 +91,10 @@ class LockerInfo(models.Model):
) )
notes = models.TextField(blank=True) notes = models.TextField(blank=True)
def clean(self):
if self.reserved and self.renter is not None:
raise ValidationError("Locker cannot both be reserved and rented!")
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint( models.UniqueConstraint(
@ -102,13 +106,6 @@ class LockerInfo(models.Model):
), ),
] ]
def __str__(self):
return f"{self.locker_unit}-{self.address} [{self.renter}]"
def clean(self):
if self.reserved and self.renter is not None:
raise ValidationError("Locker cannot both be reserved and rented!")
@property @property
def available(self) -> bool: def available(self) -> bool:
return self.renter is None and not self.reserved return self.renter is None and not self.reserved
@ -135,3 +132,6 @@ class LockerInfo(models.Model):
) )
def address(self) -> str: def address(self) -> str:
return f"{self.letter}{self.number}" return f"{self.letter}{self.number}"
def __str__(self):
return f"{self.locker_unit}-{self.address} [{self.renter}]"

View File

@ -90,18 +90,18 @@ class GroupToolSubscription(SubscriptionSettings):
group = models.ForeignKey(Group, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE)
tool = models.ForeignKey(Tool, on_delete=models.CASCADE) tool = models.ForeignKey(Tool, on_delete=models.CASCADE)
class Meta:
unique_together = (("group", "tool"),)
def __str__(self):
return f"{self.group}-{self.tool}, {super().__str__()}"
def get_task_subscriptions(self): def get_task_subscriptions(self):
for task in self.tool.task_set.all(): for task in self.tool.task_set.all():
yield GroupTaskSubscription( yield GroupTaskSubscription(
days_before=self.days_before, group=self.group, task=task days_before=self.days_before, group=self.group, task=task
) )
class Meta:
unique_together = (("group", "tool"),)
def __str__(self):
return f"{self.group}-{self.tool}, {super().__str__()}"
class GroupTaskSubscription(SubscriptionSettings): class GroupTaskSubscription(SubscriptionSettings):
group = models.ForeignKey(Group, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE)
@ -110,9 +110,6 @@ class GroupTaskSubscription(SubscriptionSettings):
class Meta: class Meta:
unique_together = (("group", "task"),) unique_together = (("group", "task"),)
def __str__(self):
return f"{self.group}-{self.task}, {super().__str__()}"
@property @property
def should_remind(self): def should_remind(self):
next_recurrence = self.task.next_recurrence next_recurrence = self.task.next_recurrence
@ -121,6 +118,9 @@ class GroupTaskSubscription(SubscriptionSettings):
time_until_overdue = next_recurrence - datetime.datetime.now() time_until_overdue = next_recurrence - datetime.datetime.now()
return self.task.is_overdue or (time_until_overdue.days <= self.days_before) return self.task.is_overdue or (time_until_overdue.days <= self.days_before)
def __str__(self):
return f"{self.group}-{self.task}, {super().__str__()}"
class Event(models.Model): class Event(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE) task = models.ForeignKey(Task, on_delete=models.CASCADE)
@ -128,9 +128,9 @@ class Event(models.Model):
date = models.DateField() date = models.DateField()
notes = MarkdownxField(blank=True) notes = MarkdownxField(blank=True)
def __str__(self):
return f"{self.task}: {self.user} {self.date}"
@property @property
def notes_html(self): def notes_html(self):
return markdown_to_clean_html(self.notes) return markdown_to_clean_html(self.notes)
def __str__(self):
return f"{self.task}: {self.user} {self.date}"