Compare commits

...

7 Commits

15 changed files with 676 additions and 189 deletions

View File

@ -37,7 +37,7 @@ INSTALLED_APPS = [
"rest_framework", "rest_framework",
"rest_framework.authtoken", "rest_framework.authtoken",
"django_q", "django_q",
"django_bleach", "django_nh3",
"tasks.apps.TasksConfig", "tasks.apps.TasksConfig",
"rentals.apps.RentalsConfig", "rentals.apps.RentalsConfig",
"membershipworks.apps.MembershipworksConfig", "membershipworks.apps.MembershipworksConfig",

View File

@ -14,6 +14,7 @@ class DoorAdmin(admin.ModelAdmin):
@admin.register(HIDEvent) @admin.register(HIDEvent)
class HIDEventAdmin(DjangoObjectActions, admin.ModelAdmin): class HIDEventAdmin(DjangoObjectActions, admin.ModelAdmin):
search_fields = ["forename", "surname", "cardholder_id"] search_fields = ["forename", "surname", "cardholder_id"]
date_hierarchy = "timestamp"
list_display = ["timestamp", "door", "event_type", "description", "_is_red"] list_display = ["timestamp", "door", "event_type", "description", "_is_red"]
list_filter = [ list_filter = [
"timestamp", "timestamp",

View File

@ -5,7 +5,15 @@ from django_object_actions import DjangoObjectActions, action
from django_q.tasks import async_task from django_q.tasks import async_task
from django_q.models import Task from django_q.models import Task
from .models import Member, Flag, Transaction from .models import (
Member,
Flag,
Transaction,
Event,
EventExt,
EventMeetingTime,
EventInstructor,
)
from .tasks.scrape import scrape_membershipworks from .tasks.scrape import scrape_membershipworks
@ -71,3 +79,57 @@ class TransactionAdmin(BaseMembershipWorksAdmin):
list_display = ["timestamp", "member", "name", "type", "sum", "note"] list_display = ["timestamp", "member", "name", "type", "sum", "note"]
list_filter = ["type"] list_filter = ["type"]
show_facets = admin.ShowFacets.ALWAYS show_facets = admin.ShowFacets.ALWAYS
search_fields = ["member", "name", "type", "note"]
date_hierarchy = "timestamp"
class EventMeetingTimeInline(admin.TabularInline):
model = EventMeetingTime
extra = 0
min_num = 1
readonly_fields = ["duration"]
# TODO: remove when switched to GeneratedField
@admin.display()
def duration(self, obj):
return obj.duration
@admin.register(EventInstructor)
class EventInstructorAdmin(admin.ModelAdmin):
autocomplete_fields = ["member"]
@admin.register(EventExt)
class EventAdmin(admin.ModelAdmin):
inlines = [EventMeetingTimeInline]
list_display = [
"title",
"start",
"end",
"duration",
"count",
"cap",
"category",
"venue",
]
list_filter = ["category", "calendar", "venue"]
show_facets = admin.ShowFacets.ALWAYS
search_fields = ["eid", "title", "url"]
date_hierarchy = "start"
readonly_fields = [
field.name
for field in Event._meta.get_fields()
if not (field.auto_created or field.many_to_many or not field.concrete)
] + ["duration"]
@admin.display()
def duration(self, obj):
return obj.duration
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False

View File

@ -7,7 +7,7 @@ def post_migrate_callback(sender, **kwargs):
from cmsmanage.django_q2_helper import ensure_scheduled from cmsmanage.django_q2_helper import ensure_scheduled
from .tasks.scrape import scrape_membershipworks from .tasks.scrape import scrape_membershipworks, scrape_events
from .tasks.ucsAccounts import sync_accounts from .tasks.ucsAccounts import sync_accounts
ensure_scheduled( ensure_scheduled(
@ -16,6 +16,12 @@ def post_migrate_callback(sender, **kwargs):
schedule_type=Schedule.HOURLY, schedule_type=Schedule.HOURLY,
) )
ensure_scheduled(
"Scrape MembershipWorks Events",
scrape_events,
schedule_type=Schedule.HOURLY,
)
ensure_scheduled( ensure_scheduled(
"Sync UCS Accounts", "Sync UCS Accounts",
sync_accounts, sync_accounts,

View File

@ -0,0 +1,8 @@
from django.core.management.base import BaseCommand
from membershipworks.tasks.scrape import scrape_events
class Command(BaseCommand):
def handle(self, *args, **options):
scrape_events()

View File

@ -238,14 +238,25 @@ class MembershipWorks:
members = self.get_members(folders, ",".join(fields.keys())) members = self.get_members(folders, ",".join(fields.keys()))
return members return members
def get_events_list(self, start_date: datetime.datetime): def get_events_list(
"""Retrive a list of events since start_date""" self,
r = self.sess.get( start_date: datetime.datetime = None,
BASE_URL + "/v2/events", end_date: datetime.datetime = None,
params={ categories=False,
"sdp": start_date.strftime("%s"), ):
}, """Retrive a list of events between `start_date` and `end_date`, optionally including category information"""
) if start_date is None and end_date is None:
raise ValueError("Must specify one of start_date or end_date")
params = {}
if start_date is not None:
params["sdp"] = start_date.strftime("%s")
if end_date is not None:
params["edp"] = end_date.strftime("%s")
if categories is not None:
params["_st"] = ""
r = self.sess.get(BASE_URL + "/v2/events", params=params)
return r.json() return r.json()
def get_event_by_eid(self, eid: str): def get_event_by_eid(self, eid: str):

View File

@ -0,0 +1,154 @@
# Generated by Django 5.0 on 2023-12-30 19:28
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("membershipworks", "0004_alter_memberflag_member_alter_transaction_member"),
]
operations = [
migrations.CreateModel(
name="Event",
fields=[
(
"eid",
models.CharField(max_length=255, primary_key=True, serialize=False),
),
("url", models.TextField()),
("title", models.TextField()),
("start", models.DateTimeField()),
("end", models.DateTimeField(blank=True, null=True)),
("cap", models.IntegerField(blank=True, null=True)),
("count", models.IntegerField()),
(
"calendar",
models.IntegerField(
choices=[
(0, "Hidden"),
(1, "Green"),
(2, "Red"),
(3, "Yellow"),
(4, "Blue"),
(5, "Purple"),
(6, "Magenta"),
(7, "Grey"),
(8, "Teal"),
]
),
),
("venue", models.TextField(blank=True, null=True)),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="EventCategory",
fields=[
("id", models.IntegerField(primary_key=True, serialize=False)),
("title", models.TextField()),
],
),
migrations.CreateModel(
name="EventExt",
fields=[
(
"event_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="membershipworks.event",
),
),
(
"materials_fee",
models.DecimalField(
blank=True, decimal_places=4, max_digits=13, null=True
),
),
],
options={
"verbose_name": "event",
},
bases=("membershipworks.event",),
),
migrations.AddField(
model_name="event",
name="category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="membershipworks.eventcategory",
),
),
migrations.CreateModel(
name="EventInstructor",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.TextField(blank=True)),
(
"member",
models.OneToOneField(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="membershipworks.member",
),
),
],
),
migrations.CreateModel(
name="EventMeetingTime",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("start", models.DateTimeField()),
("end", models.DateTimeField()),
(
"event",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="meeting_times",
to="membershipworks.eventext",
),
),
],
),
migrations.AddField(
model_name="eventext",
name="instructor",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="membershipworks.eventinstructor",
),
),
migrations.AddConstraint(
model_name="eventmeetingtime",
constraint=models.UniqueConstraint(
fields=("event", "start", "end"), name="unique_event_start_end"
),
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 5.0 on 2024-01-01 17:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"membershipworks",
"0005_event_eventcategory_eventext_event_category_and_more",
),
]
operations = [
migrations.AddField(
model_name="eventext",
name="instructor_flat_rate",
field=models.DecimalField(decimal_places=4, default=0, max_digits=13),
),
migrations.AddField(
model_name="eventext",
name="instructor_percentage",
field=models.DecimalField(decimal_places=4, default=0.5, max_digits=5),
),
]

View File

@ -4,13 +4,14 @@ from datetime import datetime
import django.core.mail.message import django.core.mail.message
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models import Exists, OuterRef from django.db.models import Exists, F, OuterRef, Sum
from django.utils import timezone from django.utils import timezone
class BaseModel(models.Model): class BaseModel(models.Model):
_csv_headers_override = {} _api_names_override = {}
_date_fields = {} _date_fields = {}
_allowed_missing_fields = []
class Meta: class Meta:
abstract = True abstract = True
@ -22,20 +23,30 @@ class BaseModel(models.Model):
if field.auto_created or field.many_to_many or not field.concrete: if field.auto_created or field.many_to_many or not field.concrete:
continue continue
field_name, csv_header = field.get_attname_column() field_name, api_name = field.get_attname_column()
if field_name in cls._csv_headers_override: if field_name in cls._api_names_override:
csv_header = cls._csv_headers_override[field.name] api_name = cls._api_names_override[field_name]
yield field_name, data[csv_header] yield field_name, data[api_name]
@classmethod @classmethod
def from_csv_dict(cls, data): def from_api_dict(cls, data):
data = data.copy() data = data.copy()
for field in cls._allowed_missing_fields:
if field not in data:
data[field] = None
# parse date fields to datetime objects # parse date fields to datetime objects
for field, fmt in cls._date_fields.items(): for field, fmt in cls._date_fields.items():
if data[field]: if data[field]:
if fmt is None:
# can't use '%s' format string, have to use the special function
data[field] = datetime.fromtimestamp(
data[field], tz=timezone.get_current_timezone()
)
else:
data[field] = datetime.strptime(str(data[field]), fmt) data[field] = datetime.strptime(str(data[field]), fmt)
else: else:
# convert empty string to None to make NULL in SQL # convert empty string to None to make NULL in SQL
@ -210,7 +221,7 @@ class Member(BaseModel):
) )
flags = models.ManyToManyField(Flag, through="MemberFlag", related_name="members") flags = models.ManyToManyField(Flag, through="MemberFlag", related_name="members")
_csv_headers_override = { _api_names_override = {
"uid": "Account ID", "uid": "Account ID",
"how_did_you_hear": "Please tell us how you heard about the Claremont MakerSpace and what tools or shops you are most excited to start using:", "how_did_you_hear": "Please tell us how you heard about the Claremont MakerSpace and what tools or shops you are most excited to start using:",
"authorize_charge": "Yes - I authorize TwinState MakerSpaces, Inc. to charge my credit card for the membership and other options that I have selected.", "authorize_charge": "Yes - I authorize TwinState MakerSpaces, Inc. to charge my credit card for the membership and other options that I have selected.",
@ -307,34 +318,161 @@ class Transaction(BaseModel):
phone = models.TextField(db_column="Phone", null=True, blank=True) phone = models.TextField(db_column="Phone", null=True, blank=True)
email = models.TextField(db_column="Email", null=True, blank=True) email = models.TextField(db_column="Email", null=True, blank=True)
@classmethod _allowed_missing_fields = [
def from_csv_dict(cls, data):
txn = data.copy()
# can't use '%s' format string, have to use the special function
txn["_dp"] = datetime.fromtimestamp(
txn["_dp"], tz=timezone.get_current_timezone()
)
allowed_missing_fields = [
"sid", "sid",
"uid", "uid",
"eid", "eid",
"fee", "fee",
"sum", "sum",
] ]
for field in allowed_missing_fields: _api_names_override = {
if field not in txn:
txn[field] = None
return super().from_csv_dict(txn)
_csv_headers_override = {
"event_id": "eid", "event_id": "eid",
"timestamp": "_dp", "timestamp": "_dp",
"type": "Transaction Type", "type": "Transaction Type",
"for_what": "Event/Form Name", "for_what": "Event/Form Name",
} }
_date_fields = {
"_dp": None,
}
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: class Meta:
db_table = "transactions" db_table = "transactions"
class EventCategory(models.Model):
id = models.IntegerField(primary_key=True)
title = models.TextField()
@classmethod
def from_api_dict(cls, id: int, data):
return cls(id=id, title=data["ttl"])
def __str__(self):
return self.title
class Event(BaseModel):
class EventCalendar(models.IntegerChoices):
HIDDEN = 0
GREEN = 1
RED = 2
YELLOW = 3
BLUE = 4
PURPLE = 5
MAGENTA = 6
GREY = 7
TEAL = 8
eid = models.CharField(max_length=255, primary_key=True)
url = models.TextField()
title = models.TextField()
start = models.DateTimeField()
end = models.DateTimeField(null=True, blank=True)
cap = models.IntegerField(null=True, blank=True)
count = models.IntegerField()
category = models.ForeignKey(EventCategory, on_delete=models.PROTECT)
calendar = models.IntegerField(choices=EventCalendar)
venue = models.TextField(null=True, blank=True)
# TODO:
# "lgo": {
# "l": "https://d1tif55lvfk8gc.cloudfront.net/656e3842ae3975908b05e304.jpg?1673405126",
# "s": "https://d1tif55lvfk8gc.cloudfront.net/656e3842ae3975908b05e304s.jpg?1673405126"
# },
_api_names_override = {
"title": "ttl",
"category_id": "grp",
"start": "sdp",
"end": "edp",
"count": "cnt",
"calendar": "cal",
"venue": "adn",
}
_date_fields = {
"sdp": None,
"edp": None,
}
_allowed_missing_fields = ["cap", "edp", "adn"]
def __str__(self):
return self.title
class EventInstructor(models.Model):
name = models.TextField(blank=True)
member = models.OneToOneField(
Member, on_delete=models.PROTECT, null=True, blank=True, db_constraint=False
)
def __str__(self):
return str(self.member) if self.member else self.name
class EventExtManager(models.Manager["EventExt"]):
def get_queryset(self) -> models.QuerySet["EventExt"]:
return (
super()
.get_queryset()
.annotate(duration=Sum(F("meeting_times__end") - F("meeting_times__start")))
)
# TODO: use simpler expression when GeneratedField fixed
# return super().get_queryset().annotate(duration=Sum("meeting_times__duration"))
class EventExt(Event):
"""Extension of `Event` to capture some fields not supported in MembershipWorks"""
objects = EventExtManager()
instructor = models.ForeignKey(
EventInstructor, on_delete=models.PROTECT, null=True, blank=True
)
materials_fee = models.DecimalField(
max_digits=13, decimal_places=4, null=True, blank=True
)
instructor_percentage = models.DecimalField(
max_digits=5, decimal_places=4, default=0.5
)
instructor_flat_rate = models.DecimalField(
max_digits=13, decimal_places=4, default=0
)
class Meta:
verbose_name = "event"
class EventMeetingTimeManager(models.Manager["EventMeetingTime"]):
def get_queryset(self) -> models.QuerySet["EventMeetingTime"]:
return super().get_queryset().annotate(duration=F("end") - F("start"))
class EventMeetingTime(models.Model):
objects = EventMeetingTimeManager()
event = models.ForeignKey(
EventExt, on_delete=models.CASCADE, related_name="meeting_times"
)
start = models.DateTimeField()
end = models.DateTimeField()
# TODO: Should use generated field instead of manager, but this is
# broken due to current Django bug, pending next release (> 5.0)
# ref: https://code.djangoproject.com/ticket/35019
# duration = models.GeneratedField(
# expression=F("end") - F("start"),
# output_field=models.DurationField(),
# db_persist=False,
# )
class Meta:
constraints = [
models.UniqueConstraint(
fields=["event", "start", "end"], name="unique_event_start_end"
)
]

View File

@ -4,12 +4,20 @@ import logging
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
from membershipworks.models import Member, Flag, Transaction from membershipworks.models import (
Member,
Flag,
Transaction,
Event,
EventExt,
EventCategory,
)
from membershipworks import MembershipWorks from membershipworks import MembershipWorks
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MAX_MEETING_TIME = timedelta(hours=6)
def flags_for_member(csv_member, all_flags, folders): def flags_for_member(csv_member, all_flags, folders):
for flag in all_flags: for flag in all_flags:
@ -50,7 +58,7 @@ def scrape_members(membershipworks: MembershipWorks):
) )
# create/update member # create/update member
member = Member.from_csv_dict(csv_member) member = Member.from_api_dict(csv_member)
member.clean_fields() member.clean_fields()
member.save() member.save()
member.flags.set(flags_for_member(csv_member, flags, folders)) member.flags.set(flags_for_member(csv_member, flags, folders))
@ -79,7 +87,7 @@ def scrape_transactions(membershipworks: MembershipWorks):
) )
for csv_transaction in transactions: for csv_transaction in transactions:
Transaction.from_csv_dict(csv_transaction).save() Transaction.from_api_dict(csv_transaction).save()
@transaction.atomic @transaction.atomic
@ -91,3 +99,45 @@ def scrape_membershipworks(*args, **options):
scrape_members(membershipworks) scrape_members(membershipworks)
scrape_transactions(membershipworks) scrape_transactions(membershipworks)
def scrape_events():
membershipworks = MembershipWorks()
membershipworks.login(
settings.MEMBERSHIPWORKS_USERNAME, settings.MEMBERSHIPWORKS_PASSWORD
)
data = membershipworks.get_events_list(
datetime.fromtimestamp(0), datetime.now() + timedelta(weeks=52), categories=True
)
logger.info(f"{len(data)} events retrieved!")
for category_id, category_data in enumerate(data["_st"]["evg"]):
category = EventCategory.from_api_dict(category_id, category_data)
category.clean_fields()
category.save()
for event_data in data["evt"]:
logger.debug(event_data)
event = Event.from_api_dict(event_data)
event.clean_fields()
event.save()
try:
event_ext = EventExt.objects.get(event_ptr=event)
except EventExt.DoesNotExist:
event_ext = EventExt(event_ptr=event)
# create extension model instance
event_ext.save_base(raw=True)
event_ext.refresh_from_db()
if (
event_ext.end is not None
and event_ext.end - event_ext.start < MAX_MEETING_TIME
):
meeting_times_count = event_ext.meeting_times.count()
if meeting_times_count == 0:
event_ext.meeting_times.create(start=event_ext.start, end=event_ext.end)
# if there is exactly one meeting time, it should match the event start/end
elif meeting_times_count == 1:
event_ext.meeting_times.update(start=event_ext.start, end=event_ext.end)

View File

@ -1,6 +1,6 @@
{% extends "base.dj.html" %} {% extends "base.dj.html" %}
{% load bleach_tags %} {% load nh3_tags %}
{% block title %}Upcoming Events{% endblock %} {% block title %}Upcoming Events{% endblock %}
{% block content %} {% block content %}
@ -73,11 +73,11 @@
{# djlint:off H006 #} {# djlint:off H006 #}
<img class="{% cycle 'alignleft' 'alignright' %}" <img class="{% cycle 'alignleft' 'alignright' %}"
width="400" width="400"
alt="Image for {{ event.ttl|bleach }}" alt="Image for {{ event.ttl|nh3 }}"
src="{{ event.lgo.l }}"> src="{{ event.lgo.l }}">
{# djlint:on #} {# djlint:on #}
{% endif %} {% endif %}
<span>{{ event.ttl|bleach }}</span> <span>{{ event.ttl|nh3 }}</span>
</a> </a>
</h2> </h2>
<!-- /wp:heading --> <!-- /wp:heading -->
@ -92,7 +92,7 @@
<!-- /wp:paragraph --> <!-- /wp:paragraph -->
{% if not section.truncate %} {% if not section.truncate %}
<!-- wp:tadv/classic-paragraph --> <!-- wp:tadv/classic-paragraph -->
<div>{{ event.dtl|bleach:"a,abbr,acronym,b,blockquote,code,em,i,li,ol,strong,ul,p,span,br,div" }}</div> <div>{{ event.dtl|nh3:"a,abbr,acronym,b,blockquote,code,em,i,li,ol,strong,ul,p,span,br,div" }}</div>
<!-- /wp:tadv/classic-paragraph --> <!-- /wp:tadv/classic-paragraph -->
<!-- wp:paragraph --> <!-- wp:paragraph -->
<p> <p>

View File

@ -107,6 +107,7 @@ class CertificationAdmin(admin.ModelAdmin):
"certification_version__definition__name", "certification_version__definition__name",
"certification_version__definition__department__name", "certification_version__definition__department__name",
] ]
date_hierarchy = "date"
autocomplete_fields = ["member"] autocomplete_fields = ["member"]
exclude = ["shop_lead_notified"] exclude = ["shop_lead_notified"]
inlines = [CertificationAuditInline] inlines = [CertificationAuditInline]

293
pdm.lock generated
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" lock_version = "4.4"
content_hash = "sha256:ce53f1fbeaf17cf8d491af18f7584e7d221bd17d074dfaec9df0099aefbc979c" content_hash = "sha256:91f554bae127245b4082d069629400706b8b43daf3bf1fb8fd963eee120ff449"
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
@ -166,7 +166,7 @@ files = [
[[package]] [[package]]
name = "black" name = "black"
version = "23.11.0" version = "23.12.1"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "The uncompromising code formatter." summary = "The uncompromising code formatter."
dependencies = [ dependencies = [
@ -177,26 +177,16 @@ dependencies = [
"platformdirs>=2", "platformdirs>=2",
] ]
files = [ files = [
{file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
{file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
{file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
{file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
{file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
{file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
] {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
{file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
[[package]] {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
name = "bleach" {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
version = "6.1.0"
requires_python = ">=3.8"
summary = "An easy safelist-based HTML-sanitizing tool."
dependencies = [
"six>=1.9.0",
"webencodings",
]
files = [
{file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"},
{file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"},
] ]
[[package]] [[package]]
@ -420,15 +410,15 @@ files = [
[[package]] [[package]]
name = "django-admin-logs" name = "django-admin-logs"
version = "1.0.2" version = "1.1.0"
requires_python = ">=3.5" requires_python = ">=3.6"
summary = "View, delete or disable Django admin log entries." summary = "View, delete or disable Django admin log entries."
dependencies = [ dependencies = [
"Django>=2.1", "Django>=3.2",
] ]
files = [ files = [
{file = "django-admin-logs-1.0.2.tar.gz", hash = "sha256:aedb5df940d32c10423d65136343bc009727df8a5a49ed0196e65241d823a890"}, {file = "django-admin-logs-1.1.0.tar.gz", hash = "sha256:bb87cd944cfa14b6d90c93584fbcdc3ffde9410fd999c65a0b524b94518a5c64"},
{file = "django_admin_logs-1.0.2-py3-none-any.whl", hash = "sha256:81753c20d372bc5562fe4a09090418bbb61b308388e851b19192873a472fa3d1"}, {file = "django_admin_logs-1.1.0-py3-none-any.whl", hash = "sha256:bb139a99a08a4b08a98731efe9112a6ba269ab0af5efcdba435d1c79706fde16"},
] ]
[[package]] [[package]]
@ -457,19 +447,6 @@ files = [
{file = "django-autocomplete-light-3.9.7.tar.gz", hash = "sha256:a34f192ac438c4df056dbfd399550799ddc631c4661960134ded924648770373"}, {file = "django-autocomplete-light-3.9.7.tar.gz", hash = "sha256:a34f192ac438c4df056dbfd399550799ddc631c4661960134ded924648770373"},
] ]
[[package]]
name = "django-bleach"
version = "1.0.0"
summary = "Easily use bleach with Django models and templates"
dependencies = [
"Django>=1.11",
"bleach>=1.5.0",
]
files = [
{file = "django-bleach-1.0.0.tar.gz", hash = "sha256:2586b90d641d4d7e70ee353570ad33d3625ed4b97036a3ea5b03ea1bb5bbeccd"},
{file = "django_bleach-1.0.0-py2.py3-none-any.whl", hash = "sha256:60074a4f4bc8d5200fdb2e03dce16fb4913427698b64570bc3e1a7ea1b8c3cf7"},
]
[[package]] [[package]]
name = "django-debug-toolbar" name = "django-debug-toolbar"
version = "4.2.0" version = "4.2.0"
@ -499,7 +476,7 @@ files = [
[[package]] [[package]]
name = "django-markdownx" name = "django-markdownx"
version = "4.0.5" version = "4.0.7"
summary = "A comprehensive Markdown editor built for Django." summary = "A comprehensive Markdown editor built for Django."
dependencies = [ dependencies = [
"Django", "Django",
@ -507,8 +484,21 @@ dependencies = [
"Pillow", "Pillow",
] ]
files = [ files = [
{file = "django-markdownx-4.0.5.tar.gz", hash = "sha256:b6007790363743aad06c70a2fa49158b4149a6226f52213b3ed3f40d790cb4d3"}, {file = "django-markdownx-4.0.7.tar.gz", hash = "sha256:38aa331c2ca0bee218b77f462361b5393e4727962bc6021939c09048363cb6ea"},
{file = "django_markdownx-4.0.5-py2.py3-none-any.whl", hash = "sha256:31cf644e38720439eb48978ea4cef8d942067408019ea8b2db802233a6377455"}, {file = "django_markdownx-4.0.7-py2.py3-none-any.whl", hash = "sha256:c1975ae3053481d4c111abd38997a5b5bb89235a1e3215f995d835942925fe7b"},
]
[[package]]
name = "django-nh3"
version = "0.1.1"
requires_python = ">=3.10"
summary = "Django integration with for nh3, Python binding to Ammonia HTML sanitizer Rust crate."
dependencies = [
"Django>=3.2",
"nh3",
]
files = [
{file = "django_nh3-0.1.1-py3-none-any.whl", hash = "sha256:10df44fd9c1d1bc5d88739094826c636c2c256ba9d89d17e4356280bb8e159a0"},
] ]
[[package]] [[package]]
@ -564,24 +554,24 @@ files = [
[[package]] [[package]]
name = "django-stubs" name = "django-stubs"
version = "4.2.6" version = "4.2.7"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Mypy stubs for Django" summary = "Mypy stubs for Django"
dependencies = [ dependencies = [
"django", "django",
"django-stubs-ext>=4.2.5", "django-stubs-ext>=4.2.7",
"types-PyYAML", "types-PyYAML",
"types-pytz", "types-pytz",
"typing-extensions", "typing-extensions",
] ]
files = [ files = [
{file = "django-stubs-4.2.6.tar.gz", hash = "sha256:e60b43de662a199db4b15c803c06669e0ac5035614af291cbd3b91591f7dcc94"}, {file = "django-stubs-4.2.7.tar.gz", hash = "sha256:8ccd2ff4ee5adf22b9e3b7b1a516d2e1c2191e9d94e672c35cc2bc3dd61e0f6b"},
{file = "django_stubs-4.2.6-py3-none-any.whl", hash = "sha256:2fcd257884a68dfa02de41ee5410ec805264d9b07d9b5b119e4dea82c7b8345e"}, {file = "django_stubs-4.2.7-py3-none-any.whl", hash = "sha256:4cf4de258fa71adc6f2799e983091b9d46cfc67c6eebc68fe111218c9a62b3b8"},
] ]
[[package]] [[package]]
name = "django-stubs-ext" name = "django-stubs-ext"
version = "4.2.5" version = "4.2.7"
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 = [
@ -589,8 +579,23 @@ dependencies = [
"typing-extensions", "typing-extensions",
] ]
files = [ files = [
{file = "django-stubs-ext-4.2.5.tar.gz", hash = "sha256:8c4d1fb5f68419b3b2474c659681a189803e27d6a5e5abf5aa0da57601b58633"}, {file = "django-stubs-ext-4.2.7.tar.gz", hash = "sha256:519342ac0849cda1559746c9a563f03ff99f636b0ebe7c14b75e816a00dfddc3"},
{file = "django_stubs_ext-4.2.5-py3-none-any.whl", hash = "sha256:921cd7ae4614e74c234bc0fe86ee75537d163addfe1fc6f134bf03e29d86c01e"}, {file = "django_stubs_ext-4.2.7-py3-none-any.whl", hash = "sha256:45a5d102417a412e3606e3c358adb4744988a92b7b58ccf3fd64bddd5d04d14c"},
]
[[package]]
name = "django-stubs"
version = "4.2.7"
extras = ["compatible-mypy"]
requires_python = ">=3.8"
summary = "Mypy stubs for Django"
dependencies = [
"django-stubs==4.2.7",
"mypy~=1.7.0",
]
files = [
{file = "django-stubs-4.2.7.tar.gz", hash = "sha256:8ccd2ff4ee5adf22b9e3b7b1a516d2e1c2191e9d94e672c35cc2bc3dd61e0f6b"},
{file = "django_stubs-4.2.7-py3-none-any.whl", hash = "sha256:4cf4de258fa71adc6f2799e983091b9d46cfc67c6eebc68fe111218c9a62b3b8"},
] ]
[[package]] [[package]]
@ -619,40 +624,40 @@ files = [
[[package]] [[package]]
name = "djangorestframework-stubs" name = "djangorestframework-stubs"
version = "3.14.4" version = "3.14.5"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "PEP-484 stubs for django-rest-framework" summary = "PEP-484 stubs for django-rest-framework"
dependencies = [ dependencies = [
"django-stubs>=4.2.5", "django-stubs>=4.2.7",
"mypy>=0.991",
"requests>=2.0.0", "requests>=2.0.0",
"types-PyYAML>=5.4.3", "types-PyYAML>=5.4.3",
"types-requests>=0.1.12", "types-requests>=0.1.12",
"typing-extensions>=3.10.0", "typing-extensions>=3.10.0",
] ]
files = [ files = [
{file = "djangorestframework-stubs-3.14.4.tar.gz", hash = "sha256:8ee8719bfeb647b92cc200e15b3cc9813d2e4468c8190777a55a121542a4b2d4"}, {file = "djangorestframework-stubs-3.14.5.tar.gz", hash = "sha256:5dd6f638aa5291fb7863e6166128a6ed20bf4986e2fc5cf334e6afc841797a09"},
{file = "djangorestframework_stubs-3.14.4-py3-none-any.whl", hash = "sha256:5be8275dd05d6629b3d1688929586ef7b6bc66b4f3f728b5e0389305f07c7a7f"}, {file = "djangorestframework_stubs-3.14.5-py3-none-any.whl", hash = "sha256:43d788fd50cda49b922cd411e59c5b8cdc3f3de49c02febae12ce42139f0269b"},
] ]
[[package]] [[package]]
name = "djangorestframework-stubs" name = "djangorestframework-stubs"
version = "3.14.4" version = "3.14.5"
extras = ["compatible-mypy"] extras = ["compatible-mypy"]
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "PEP-484 stubs for django-rest-framework" summary = "PEP-484 stubs for django-rest-framework"
dependencies = [ dependencies = [
"djangorestframework-stubs==3.14.4", "django-stubs[compatible-mypy]",
"mypy~=1.6.0", "djangorestframework-stubs==3.14.5",
"mypy~=1.7.0",
] ]
files = [ files = [
{file = "djangorestframework-stubs-3.14.4.tar.gz", hash = "sha256:8ee8719bfeb647b92cc200e15b3cc9813d2e4468c8190777a55a121542a4b2d4"}, {file = "djangorestframework-stubs-3.14.5.tar.gz", hash = "sha256:5dd6f638aa5291fb7863e6166128a6ed20bf4986e2fc5cf334e6afc841797a09"},
{file = "djangorestframework_stubs-3.14.4-py3-none-any.whl", hash = "sha256:5be8275dd05d6629b3d1688929586ef7b6bc66b4f3f728b5e0389305f07c7a7f"}, {file = "djangorestframework_stubs-3.14.5-py3-none-any.whl", hash = "sha256:43d788fd50cda49b922cd411e59c5b8cdc3f3de49c02febae12ce42139f0269b"},
] ]
[[package]] [[package]]
name = "djlint" name = "djlint"
version = "1.34.0" version = "1.34.1"
requires_python = ">=3.8.0,<4.0.0" requires_python = ">=3.8.0,<4.0.0"
summary = "HTML Template Linter and Formatter" summary = "HTML Template Linter and Formatter"
dependencies = [ dependencies = [
@ -664,13 +669,13 @@ dependencies = [
"html-void-elements<0.2.0,>=0.1.0", "html-void-elements<0.2.0,>=0.1.0",
"jsbeautifier<2.0.0,>=1.14.4", "jsbeautifier<2.0.0,>=1.14.4",
"json5<0.10.0,>=0.9.11", "json5<0.10.0,>=0.9.11",
"pathspec<0.12.0,>=0.11.0", "pathspec<0.13.0,>=0.12.0",
"regex<2024.0.0,>=2023.0.0", "regex<2024.0.0,>=2023.0.0",
"tqdm<5.0.0,>=4.62.2", "tqdm<5.0.0,>=4.62.2",
] ]
files = [ files = [
{file = "djlint-1.34.0-py3-none-any.whl", hash = "sha256:bdc26cc607dee8b46e262654eb0fbac7862c34d68172c8adc25a0b56fc7d8173"}, {file = "djlint-1.34.1-py3-none-any.whl", hash = "sha256:96ff1c464fb6f061130ebc88663a2ea524d7ec51f4b56221a2b3f0320a3cfce8"},
{file = "djlint-1.34.0.tar.gz", hash = "sha256:60b4f4ca99fd83106603bdd466f35314fda33776f3a6e70ea9d674da9d0ad053"}, {file = "djlint-1.34.1.tar.gz", hash = "sha256:db93fa008d19eaadb0454edf1704931d14469d48508daba2df9941111f408346"},
] ]
[[package]] [[package]]
@ -869,8 +874,8 @@ files = [
[[package]] [[package]]
name = "ipython" name = "ipython"
version = "8.18.1" version = "8.19.0"
requires_python = ">=3.9" requires_python = ">=3.10"
summary = "IPython: Productive Interactive Computing" summary = "IPython: Productive Interactive Computing"
dependencies = [ dependencies = [
"colorama; sys_platform == \"win32\"", "colorama; sys_platform == \"win32\"",
@ -884,8 +889,8 @@ dependencies = [
"traitlets>=5", "traitlets>=5",
] ]
files = [ files = [
{file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.19.0-py3-none-any.whl", hash = "sha256:2f55d59370f59d0d2b2212109fe0e6035cfea436b1c0e6150ad2244746272ec5"},
{file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, {file = "ipython-8.19.0.tar.gz", hash = "sha256:ac4da4ecf0042fb4e0ce57c60430c2db3c719fa8bdf92f8631d6bd8a5785d1f0"},
] ]
[[package]] [[package]]
@ -924,40 +929,41 @@ files = [
[[package]] [[package]]
name = "lxml" name = "lxml"
version = "4.9.3" version = "5.0.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
files = [ files = [
{file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, {file = "lxml-5.0.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5382612ba2424cea5d2c89e2c29077023d8de88f8d60d5ceff5f76334516df9e"},
{file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, {file = "lxml-5.0.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:07a900735bad9af7be3085480bf384f68ed5580ba465b39a098e6a882c060d6b"},
{file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:980ba47c8db4b9d870014c7040edb230825b79017a6a27aa54cdb6fcc02d8cc0"},
{file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6507c58431dbd95b50654b3313c5ad54f90e54e5f2cdacf733de61eae478eec5"},
{file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4a45a278518e4308865c1e9dbb2c42ce84fb154efb03adeb16fdae3c1687c7c9"},
{file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:59cea9ba1c675fbd6867ca1078fc717a113e7f5b7644943b74137b7cc55abebf"},
{file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd39ef87fd1f7bb5c4aa53454936e6135cbfe03fe3744e8218be193f9e4fef16"},
{file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e6bb39d91bf932e7520cb5718ae3c2f498052aca53294d5d59fdd9068fe1a7f2"},
{file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, {file = "lxml-5.0.0-cp311-cp311-win32.whl", hash = "sha256:21af2c3862db6f4f486cddf73ec1157b40d5828876c47cd880edcbad8240ea1b"},
{file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, {file = "lxml-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:c1249aa4eaced30b59ecf8b8cae0b1ccede04583c74ca7d10b6f8bbead908b2c"},
{file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, {file = "lxml-5.0.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f30e697b6215e759d0824768b2c5b0618d2dc19abe6c67eeed2b0460f52470d1"},
{file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d1bb64646480c36a4aa1b6a44a5b6e33d0fcbeab9f53f1b39072cd3bb2c6243a"},
{file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4e69c36c8618707a90ed3fb6f48a6cc9254ffcdbf7b259e439a5ae5fbf9c5206"},
{file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca498f8554a09fbc3a2f8fc4b23261e07bc27bef99b3df98e2570688033f6fc"},
{file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0326e9b8176ea77269fb39e7af4010906e73e9496a9f8eaf06d253b1b1231ceb"},
{file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, {file = "lxml-5.0.0-cp312-cp312-win32.whl", hash = "sha256:5fb988e15378d6e905ca8f60813950a0c56da9469d0e8e5d8fe785b282684ec5"},
{file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, {file = "lxml-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:bb58e8f4b2cfe012cd312239b8d5139995fe8f5945c7c26d5fbbbb1ddb9acd47"},
{file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, {file = "lxml-5.0.0-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:96095bfc0c02072fc89afa67626013a253596ea5118b8a7f4daaae049dafa096"},
{file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, {file = "lxml-5.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:992029258ed719f130d5a9c443d142c32843046f1263f2c492862b2a853be570"},
{file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, {file = "lxml-5.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:db40e85cffd22f7d65dcce30e85af565a66401a6ed22fc0c56ed342cfa4ffc43"},
{file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, {file = "lxml-5.0.0-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:cfa8a4cdc3765574b7fd0c7cfa5fbd1e2108014c9dfd299c679e5152bea9a55e"},
{file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:049fef98d02513c34f5babd07569fc1cf1ed14c0f2fbff18fe72597f977ef3c2"},
{file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a85136d0ee18a41c91cc3e2844c683be0e72e6dda4cb58da9e15fcaef3726af7"},
{file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:766868f729f3ab84125350f1a0ea2594d8b1628a608a574542a5aff7355b9941"},
{file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, {file = "lxml-5.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99cad5c912f359e59e921689c04e54662cdd80835d80eeaa931e22612f515df7"},
{file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, {file = "lxml-5.0.0-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:c90c593aa8dd57d5dab0ef6d7d64af894008971d98e6a41b320fdd75258fbc6e"},
{file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8134d5441d1ed6a682e3de3d7a98717a328dce619ee9c4c8b3b91f0cb0eb3e28"},
{file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f298ac9149037d6a3d5c74991bded39ac46292520b9c7c182cb102486cc87677"},
{file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:894c5f71186b410679aaab5774543fcb9cbabe8893f0b31d11cf28a0740e80be"},
{file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, {file = "lxml-5.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9cd3d6c2c67d4fdcd795e4945e2ba5434909c96640b4cc09453bd0dc7e8e1bac"},
{file = "lxml-5.0.0.zip", hash = "sha256:2219cbf790e701acf9a21a31ead75f983e73daf0eceb9da6990212e4d20ebefe"},
] ]
[[package]] [[package]]
@ -1110,7 +1116,7 @@ files = [
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "1.6.1" version = "1.7.1"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Optional static typing for Python" summary = "Optional static typing for Python"
dependencies = [ dependencies = [
@ -1118,18 +1124,18 @@ dependencies = [
"typing-extensions>=4.1.0", "typing-extensions>=4.1.0",
] ]
files = [ files = [
{file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"},
{file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"},
{file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"},
{file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"},
{file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"},
{file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"},
{file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"},
{file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"},
{file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"},
{file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"},
{file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"},
{file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"},
] ]
[[package]] [[package]]
@ -1144,14 +1150,39 @@ files = [
[[package]] [[package]]
name = "mysqlclient" name = "mysqlclient"
version = "2.2.0" version = "2.2.1"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Python interface to MySQL" summary = "Python interface to MySQL"
files = [ files = [
{file = "mysqlclient-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5670679ff1be1cc3fef0fa81bf39f0cd70605ba121141050f02743eb878ac114"}, {file = "mysqlclient-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:8f40c872f19639366e3df27bef2ff087be0e3ee0bd3453470bd29f46b54a90f6"},
{file = "mysqlclient-2.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:955dba905a7443ce4788c63fdb9f8d688316260cf60b20ff51ac3b1c77616ede"}, {file = "mysqlclient-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:45600f4f321096bd1ead3355bc62cfcf8d97dc78df94e4ab5db72ecb5db1bd04"},
{file = "mysqlclient-2.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:530ece9995a36cadb6211b9787f0c9e05cdab6702549bdb4236af5e9b535ed6a"}, {file = "mysqlclient-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:97eee76818774bb695e018ff4c3dafaab74b9a0b0cf32c90b02caeec3b19cd8e"},
{file = "mysqlclient-2.2.0.tar.gz", hash = "sha256:04368445f9c487d8abb7a878e3d23e923e6072c04a6c320f9e0dc8a82efba14e"}, {file = "mysqlclient-2.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4fabe1f4b545ed6244ad0ff426e6b27054b7e5c5b1392be0de2e5f2f59be0392"},
{file = "mysqlclient-2.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:641a7c9de443ddef186a0e89f24b4251ad44f4ddc5e7094332bf2d286d7c9e33"},
{file = "mysqlclient-2.2.1.tar.gz", hash = "sha256:2c7ad15b87293b12fd44b47c46879ec95ec647f4567e866ccd70b8337584e9b2"},
]
[[package]]
name = "nh3"
version = "0.2.15"
summary = "Python bindings to the ammonia HTML sanitization library."
files = [
{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]] [[package]]
@ -1191,12 +1222,12 @@ files = [
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.11.0" version = "0.12.1"
requires_python = ">=3.7" requires_python = ">=3.8"
summary = "Utility library for gitignore style pattern matching of file paths." summary = "Utility library for gitignore style pattern matching of file paths."
files = [ files = [
{file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
] ]
[[package]] [[package]]
@ -1535,12 +1566,12 @@ files = [
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "69.0.2" version = "69.0.3"
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-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
{file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
] ]
[[package]] [[package]]
@ -1599,15 +1630,15 @@ files = [
[[package]] [[package]]
name = "tinycss2" name = "tinycss2"
version = "1.2.1" version = "1.1.1"
requires_python = ">=3.7" requires_python = ">=3.6"
summary = "A tiny CSS parser" summary = "A tiny CSS parser"
dependencies = [ dependencies = [
"webencodings>=0.4", "webencodings>=0.4",
] ]
files = [ files = [
{file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, {file = "tinycss2-1.1.1-py3-none-any.whl", hash = "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8"},
{file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, {file = "tinycss2-1.1.1.tar.gz", hash = "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf"},
] ]
[[package]] [[package]]
@ -1847,7 +1878,7 @@ files = [
[[package]] [[package]]
name = "weasyprint" name = "weasyprint"
version = "60.1" version = "60.2"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "The Awesome Document Factory" summary = "The Awesome Document Factory"
dependencies = [ dependencies = [
@ -1861,8 +1892,8 @@ dependencies = [
"tinycss2>=1.0.0", "tinycss2>=1.0.0",
] ]
files = [ files = [
{file = "weasyprint-60.1-py3-none-any.whl", hash = "sha256:55227e5e44f5f34bc9cec651329bd38d063ef7d29151d4b058d4af1ca943d4a7"}, {file = "weasyprint-60.2-py3-none-any.whl", hash = "sha256:3e98eedcc1c5a14cb310c293c6d59a479f59a13f0d705ff07106482827fa5705"},
{file = "weasyprint-60.1.tar.gz", hash = "sha256:56b9812280118357b0f63b1efe18199e08343d4a56a3393c1d475ab878cea26a"}, {file = "weasyprint-60.2.tar.gz", hash = "sha256:0c0cdd617a78699262b80026e67fa1692e3802cfa966395436eeaf6f787dd126"},
] ]
[[package]] [[package]]

View File

@ -7,7 +7,7 @@ authors = [
] ]
dependencies = [ dependencies = [
"django~=5.0", "django~=5.0",
"django-admin-logs~=1.0", "django-admin-logs~=1.1",
"django-auth-ldap~=4.6", "django-auth-ldap~=4.6",
"django-markdownx~=4.0", "django-markdownx~=4.0",
"django-recurrence~=1.11", "django-recurrence~=1.11",
@ -17,18 +17,18 @@ dependencies = [
"mdformat~=0.7", "mdformat~=0.7",
"mdformat-tables~=0.4", "mdformat-tables~=0.4",
"mysqlclient~=2.2", "mysqlclient~=2.2",
"bleach~=6.1",
"django-autocomplete-light~=3.9", "django-autocomplete-light~=3.9",
"weasyprint~=60.1", "weasyprint~=60.2",
"requests~=2.31", "requests~=2.31",
"semver~=3.0", "semver~=3.0",
"djangorestframework~=3.14", "djangorestframework~=3.14",
"django-q2~=1.6", "django-q2~=1.6",
"lxml~=4.9", "lxml~=5.0",
"django-object-actions~=4.2", "django-object-actions~=4.2",
"udm-rest-client~=1.2", "udm-rest-client~=1.2",
"openapi-client-udm~=1.0", "openapi-client-udm~=1.0",
"django-bleach~=1.0", "django-nh3~=0.1",
"nh3~=0.2",
] ]
requires-python = ">=3.11" requires-python = ">=3.11"
@ -82,11 +82,11 @@ include_packages = ["openapi-client-udm"]
[tool.pdm.dev-dependencies] [tool.pdm.dev-dependencies]
lint = [ lint = [
"black~=23.11", "black~=23.12",
"djlint~=1.34", "djlint~=1.34",
] ]
typing = [ typing = [
"mypy~=1.6", "mypy~=1.7",
"django-stubs~=4.2", "django-stubs~=4.2",
"setuptools~=69.0", "setuptools~=69.0",
"types-bleach~=6.1", "types-bleach~=6.1",
@ -100,7 +100,7 @@ debug = [
] ]
dev = [ dev = [
"django-extensions~=3.2", "django-extensions~=3.2",
"ipython~=8.18", "ipython~=8.19",
] ]
[tool.pdm.scripts] [tool.pdm.scripts]

View File

@ -1,4 +1,4 @@
import bleach import nh3
from markdownx.utils import markdownify from markdownx.utils import markdownify
# fmt: off # fmt: off
@ -23,5 +23,5 @@ MARKDOWN_ATTRS = {
def markdown_to_clean_html(md: str) -> str: def markdown_to_clean_html(md: str) -> str:
x = bleach.clean(markdownify(md), tags=MARKDOWN_TAGS, attributes=MARKDOWN_ATTRS) x = nh3.clean(markdownify(md), tags=MARKDOWN_TAGS, attributes=MARKDOWN_ATTRS)
return x return x