Compare commits
7 Commits
0ab7da0463
...
f2332dbe37
Author | SHA1 | Date | |
---|---|---|---|
f2332dbe37 | |||
f5688e39c3 | |||
546b13428e | |||
08dde9a4f4 | |||
02986bdabc | |||
d7e919f6f0 | |||
ee7c18f575 |
@ -37,7 +37,7 @@ INSTALLED_APPS = [
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
"django_q",
|
||||
"django_bleach",
|
||||
"django_nh3",
|
||||
"tasks.apps.TasksConfig",
|
||||
"rentals.apps.RentalsConfig",
|
||||
"membershipworks.apps.MembershipworksConfig",
|
||||
|
@ -14,6 +14,7 @@ class DoorAdmin(admin.ModelAdmin):
|
||||
@admin.register(HIDEvent)
|
||||
class HIDEventAdmin(DjangoObjectActions, admin.ModelAdmin):
|
||||
search_fields = ["forename", "surname", "cardholder_id"]
|
||||
date_hierarchy = "timestamp"
|
||||
list_display = ["timestamp", "door", "event_type", "description", "_is_red"]
|
||||
list_filter = [
|
||||
"timestamp",
|
||||
|
@ -5,7 +5,15 @@ from django_object_actions import DjangoObjectActions, action
|
||||
from django_q.tasks import async_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
|
||||
|
||||
|
||||
@ -71,3 +79,57 @@ class TransactionAdmin(BaseMembershipWorksAdmin):
|
||||
list_display = ["timestamp", "member", "name", "type", "sum", "note"]
|
||||
list_filter = ["type"]
|
||||
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
|
||||
|
@ -7,7 +7,7 @@ def post_migrate_callback(sender, **kwargs):
|
||||
|
||||
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
|
||||
|
||||
ensure_scheduled(
|
||||
@ -16,6 +16,12 @@ def post_migrate_callback(sender, **kwargs):
|
||||
schedule_type=Schedule.HOURLY,
|
||||
)
|
||||
|
||||
ensure_scheduled(
|
||||
"Scrape MembershipWorks Events",
|
||||
scrape_events,
|
||||
schedule_type=Schedule.HOURLY,
|
||||
)
|
||||
|
||||
ensure_scheduled(
|
||||
"Sync UCS Accounts",
|
||||
sync_accounts,
|
||||
|
8
membershipworks/management/commands/scrape_events.py
Normal file
8
membershipworks/management/commands/scrape_events.py
Normal 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()
|
@ -238,14 +238,25 @@ class MembershipWorks:
|
||||
members = self.get_members(folders, ",".join(fields.keys()))
|
||||
return members
|
||||
|
||||
def get_events_list(self, start_date: datetime.datetime):
|
||||
"""Retrive a list of events since start_date"""
|
||||
r = self.sess.get(
|
||||
BASE_URL + "/v2/events",
|
||||
params={
|
||||
"sdp": start_date.strftime("%s"),
|
||||
},
|
||||
)
|
||||
def get_events_list(
|
||||
self,
|
||||
start_date: datetime.datetime = None,
|
||||
end_date: datetime.datetime = None,
|
||||
categories=False,
|
||||
):
|
||||
"""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()
|
||||
|
||||
def get_event_by_eid(self, eid: str):
|
||||
|
@ -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"
|
||||
),
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -4,13 +4,14 @@ from datetime import datetime
|
||||
import django.core.mail.message
|
||||
from django.conf import settings
|
||||
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
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
_csv_headers_override = {}
|
||||
_api_names_override = {}
|
||||
_date_fields = {}
|
||||
_allowed_missing_fields = []
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -22,21 +23,31 @@ class BaseModel(models.Model):
|
||||
if field.auto_created or field.many_to_many or not field.concrete:
|
||||
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:
|
||||
csv_header = cls._csv_headers_override[field.name]
|
||||
if field_name in cls._api_names_override:
|
||||
api_name = cls._api_names_override[field_name]
|
||||
|
||||
yield field_name, data[csv_header]
|
||||
yield field_name, data[api_name]
|
||||
|
||||
@classmethod
|
||||
def from_csv_dict(cls, data):
|
||||
def from_api_dict(cls, data):
|
||||
data = data.copy()
|
||||
|
||||
for field in cls._allowed_missing_fields:
|
||||
if field not in data:
|
||||
data[field] = None
|
||||
|
||||
# parse date fields to datetime objects
|
||||
for field, fmt in cls._date_fields.items():
|
||||
if data[field]:
|
||||
data[field] = datetime.strptime(str(data[field]), fmt)
|
||||
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)
|
||||
else:
|
||||
# convert empty string to None to make NULL in SQL
|
||||
data[field] = None
|
||||
@ -210,7 +221,7 @@ class Member(BaseModel):
|
||||
)
|
||||
flags = models.ManyToManyField(Flag, through="MemberFlag", related_name="members")
|
||||
|
||||
_csv_headers_override = {
|
||||
_api_names_override = {
|
||||
"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:",
|
||||
"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)
|
||||
email = models.TextField(db_column="Email", null=True, blank=True)
|
||||
|
||||
@classmethod
|
||||
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",
|
||||
"uid",
|
||||
"eid",
|
||||
"fee",
|
||||
"sum",
|
||||
]
|
||||
for field in allowed_missing_fields:
|
||||
if field not in txn:
|
||||
txn[field] = None
|
||||
return super().from_csv_dict(txn)
|
||||
|
||||
_csv_headers_override = {
|
||||
_allowed_missing_fields = [
|
||||
"sid",
|
||||
"uid",
|
||||
"eid",
|
||||
"fee",
|
||||
"sum",
|
||||
]
|
||||
_api_names_override = {
|
||||
"event_id": "eid",
|
||||
"timestamp": "_dp",
|
||||
"type": "Transaction Type",
|
||||
"for_what": "Event/Form Name",
|
||||
}
|
||||
_date_fields = {
|
||||
"_dp": None,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.type} [{self.member if self.member else self.name}] {self.timestamp}"
|
||||
|
||||
class Meta:
|
||||
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"
|
||||
)
|
||||
]
|
||||
|
@ -4,12 +4,20 @@ import logging
|
||||
from django.conf import settings
|
||||
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
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_MEETING_TIME = timedelta(hours=6)
|
||||
|
||||
|
||||
def flags_for_member(csv_member, all_flags, folders):
|
||||
for flag in all_flags:
|
||||
@ -50,7 +58,7 @@ def scrape_members(membershipworks: MembershipWorks):
|
||||
)
|
||||
|
||||
# create/update member
|
||||
member = Member.from_csv_dict(csv_member)
|
||||
member = Member.from_api_dict(csv_member)
|
||||
member.clean_fields()
|
||||
member.save()
|
||||
member.flags.set(flags_for_member(csv_member, flags, folders))
|
||||
@ -79,7 +87,7 @@ def scrape_transactions(membershipworks: MembershipWorks):
|
||||
)
|
||||
|
||||
for csv_transaction in transactions:
|
||||
Transaction.from_csv_dict(csv_transaction).save()
|
||||
Transaction.from_api_dict(csv_transaction).save()
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@ -91,3 +99,45 @@ def scrape_membershipworks(*args, **options):
|
||||
|
||||
scrape_members(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)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends "base.dj.html" %}
|
||||
|
||||
{% load bleach_tags %}
|
||||
{% load nh3_tags %}
|
||||
|
||||
{% block title %}Upcoming Events{% endblock %}
|
||||
{% block content %}
|
||||
@ -73,11 +73,11 @@
|
||||
{# djlint:off H006 #}
|
||||
<img class="{% cycle 'alignleft' 'alignright' %}"
|
||||
width="400"
|
||||
alt="Image for {{ event.ttl|bleach }}"
|
||||
alt="Image for {{ event.ttl|nh3 }}"
|
||||
src="{{ event.lgo.l }}">
|
||||
{# djlint:on #}
|
||||
{% endif %}
|
||||
<span>{{ event.ttl|bleach }}</span>
|
||||
<span>{{ event.ttl|nh3 }}</span>
|
||||
</a>
|
||||
</h2>
|
||||
<!-- /wp:heading -->
|
||||
@ -92,7 +92,7 @@
|
||||
<!-- /wp:paragraph -->
|
||||
{% if not section.truncate %}
|
||||
<!-- 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:paragraph -->
|
||||
<p>
|
||||
|
@ -107,6 +107,7 @@ class CertificationAdmin(admin.ModelAdmin):
|
||||
"certification_version__definition__name",
|
||||
"certification_version__definition__department__name",
|
||||
]
|
||||
date_hierarchy = "date"
|
||||
autocomplete_fields = ["member"]
|
||||
exclude = ["shop_lead_notified"]
|
||||
inlines = [CertificationAuditInline]
|
||||
|
293
pdm.lock
generated
293
pdm.lock
generated
@ -5,7 +5,7 @@
|
||||
groups = ["default", "debug", "lint", "server", "typing", "dev"]
|
||||
strategy = ["cross_platform"]
|
||||
lock_version = "4.4"
|
||||
content_hash = "sha256:ce53f1fbeaf17cf8d491af18f7584e7d221bd17d074dfaec9df0099aefbc979c"
|
||||
content_hash = "sha256:91f554bae127245b4082d069629400706b8b43daf3bf1fb8fd963eee120ff449"
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
@ -166,7 +166,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "23.11.0"
|
||||
version = "23.12.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "The uncompromising code formatter."
|
||||
dependencies = [
|
||||
@ -177,26 +177,16 @@ dependencies = [
|
||||
"platformdirs>=2",
|
||||
]
|
||||
files = [
|
||||
{file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"},
|
||||
{file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"},
|
||||
{file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"},
|
||||
{file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"},
|
||||
{file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"},
|
||||
{file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bleach"
|
||||
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"},
|
||||
{file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
|
||||
{file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
|
||||
{file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
|
||||
{file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
|
||||
{file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
|
||||
{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"},
|
||||
{file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
|
||||
{file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -420,15 +410,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "django-admin-logs"
|
||||
version = "1.0.2"
|
||||
requires_python = ">=3.5"
|
||||
version = "1.1.0"
|
||||
requires_python = ">=3.6"
|
||||
summary = "View, delete or disable Django admin log entries."
|
||||
dependencies = [
|
||||
"Django>=2.1",
|
||||
"Django>=3.2",
|
||||
]
|
||||
files = [
|
||||
{file = "django-admin-logs-1.0.2.tar.gz", hash = "sha256:aedb5df940d32c10423d65136343bc009727df8a5a49ed0196e65241d823a890"},
|
||||
{file = "django_admin_logs-1.0.2-py3-none-any.whl", hash = "sha256:81753c20d372bc5562fe4a09090418bbb61b308388e851b19192873a472fa3d1"},
|
||||
{file = "django-admin-logs-1.1.0.tar.gz", hash = "sha256:bb87cd944cfa14b6d90c93584fbcdc3ffde9410fd999c65a0b524b94518a5c64"},
|
||||
{file = "django_admin_logs-1.1.0-py3-none-any.whl", hash = "sha256:bb139a99a08a4b08a98731efe9112a6ba269ab0af5efcdba435d1c79706fde16"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -457,19 +447,6 @@ files = [
|
||||
{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]]
|
||||
name = "django-debug-toolbar"
|
||||
version = "4.2.0"
|
||||
@ -499,7 +476,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "django-markdownx"
|
||||
version = "4.0.5"
|
||||
version = "4.0.7"
|
||||
summary = "A comprehensive Markdown editor built for Django."
|
||||
dependencies = [
|
||||
"Django",
|
||||
@ -507,8 +484,21 @@ dependencies = [
|
||||
"Pillow",
|
||||
]
|
||||
files = [
|
||||
{file = "django-markdownx-4.0.5.tar.gz", hash = "sha256:b6007790363743aad06c70a2fa49158b4149a6226f52213b3ed3f40d790cb4d3"},
|
||||
{file = "django_markdownx-4.0.5-py2.py3-none-any.whl", hash = "sha256:31cf644e38720439eb48978ea4cef8d942067408019ea8b2db802233a6377455"},
|
||||
{file = "django-markdownx-4.0.7.tar.gz", hash = "sha256:38aa331c2ca0bee218b77f462361b5393e4727962bc6021939c09048363cb6ea"},
|
||||
{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]]
|
||||
@ -564,24 +554,24 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "django-stubs"
|
||||
version = "4.2.6"
|
||||
version = "4.2.7"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Mypy stubs for Django"
|
||||
dependencies = [
|
||||
"django",
|
||||
"django-stubs-ext>=4.2.5",
|
||||
"django-stubs-ext>=4.2.7",
|
||||
"types-PyYAML",
|
||||
"types-pytz",
|
||||
"typing-extensions",
|
||||
]
|
||||
files = [
|
||||
{file = "django-stubs-4.2.6.tar.gz", hash = "sha256:e60b43de662a199db4b15c803c06669e0ac5035614af291cbd3b91591f7dcc94"},
|
||||
{file = "django_stubs-4.2.6-py3-none-any.whl", hash = "sha256:2fcd257884a68dfa02de41ee5410ec805264d9b07d9b5b119e4dea82c7b8345e"},
|
||||
{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]]
|
||||
name = "django-stubs-ext"
|
||||
version = "4.2.5"
|
||||
version = "4.2.7"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Monkey-patching and extensions for django-stubs"
|
||||
dependencies = [
|
||||
@ -589,8 +579,23 @@ dependencies = [
|
||||
"typing-extensions",
|
||||
]
|
||||
files = [
|
||||
{file = "django-stubs-ext-4.2.5.tar.gz", hash = "sha256:8c4d1fb5f68419b3b2474c659681a189803e27d6a5e5abf5aa0da57601b58633"},
|
||||
{file = "django_stubs_ext-4.2.5-py3-none-any.whl", hash = "sha256:921cd7ae4614e74c234bc0fe86ee75537d163addfe1fc6f134bf03e29d86c01e"},
|
||||
{file = "django-stubs-ext-4.2.7.tar.gz", hash = "sha256:519342ac0849cda1559746c9a563f03ff99f636b0ebe7c14b75e816a00dfddc3"},
|
||||
{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]]
|
||||
@ -619,40 +624,40 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "djangorestframework-stubs"
|
||||
version = "3.14.4"
|
||||
version = "3.14.5"
|
||||
requires_python = ">=3.8"
|
||||
summary = "PEP-484 stubs for django-rest-framework"
|
||||
dependencies = [
|
||||
"django-stubs>=4.2.5",
|
||||
"mypy>=0.991",
|
||||
"django-stubs>=4.2.7",
|
||||
"requests>=2.0.0",
|
||||
"types-PyYAML>=5.4.3",
|
||||
"types-requests>=0.1.12",
|
||||
"typing-extensions>=3.10.0",
|
||||
]
|
||||
files = [
|
||||
{file = "djangorestframework-stubs-3.14.4.tar.gz", hash = "sha256:8ee8719bfeb647b92cc200e15b3cc9813d2e4468c8190777a55a121542a4b2d4"},
|
||||
{file = "djangorestframework_stubs-3.14.4-py3-none-any.whl", hash = "sha256:5be8275dd05d6629b3d1688929586ef7b6bc66b4f3f728b5e0389305f07c7a7f"},
|
||||
{file = "djangorestframework-stubs-3.14.5.tar.gz", hash = "sha256:5dd6f638aa5291fb7863e6166128a6ed20bf4986e2fc5cf334e6afc841797a09"},
|
||||
{file = "djangorestframework_stubs-3.14.5-py3-none-any.whl", hash = "sha256:43d788fd50cda49b922cd411e59c5b8cdc3f3de49c02febae12ce42139f0269b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "djangorestframework-stubs"
|
||||
version = "3.14.4"
|
||||
version = "3.14.5"
|
||||
extras = ["compatible-mypy"]
|
||||
requires_python = ">=3.8"
|
||||
summary = "PEP-484 stubs for django-rest-framework"
|
||||
dependencies = [
|
||||
"djangorestframework-stubs==3.14.4",
|
||||
"mypy~=1.6.0",
|
||||
"django-stubs[compatible-mypy]",
|
||||
"djangorestframework-stubs==3.14.5",
|
||||
"mypy~=1.7.0",
|
||||
]
|
||||
files = [
|
||||
{file = "djangorestframework-stubs-3.14.4.tar.gz", hash = "sha256:8ee8719bfeb647b92cc200e15b3cc9813d2e4468c8190777a55a121542a4b2d4"},
|
||||
{file = "djangorestframework_stubs-3.14.4-py3-none-any.whl", hash = "sha256:5be8275dd05d6629b3d1688929586ef7b6bc66b4f3f728b5e0389305f07c7a7f"},
|
||||
{file = "djangorestframework-stubs-3.14.5.tar.gz", hash = "sha256:5dd6f638aa5291fb7863e6166128a6ed20bf4986e2fc5cf334e6afc841797a09"},
|
||||
{file = "djangorestframework_stubs-3.14.5-py3-none-any.whl", hash = "sha256:43d788fd50cda49b922cd411e59c5b8cdc3f3de49c02febae12ce42139f0269b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "djlint"
|
||||
version = "1.34.0"
|
||||
version = "1.34.1"
|
||||
requires_python = ">=3.8.0,<4.0.0"
|
||||
summary = "HTML Template Linter and Formatter"
|
||||
dependencies = [
|
||||
@ -664,13 +669,13 @@ dependencies = [
|
||||
"html-void-elements<0.2.0,>=0.1.0",
|
||||
"jsbeautifier<2.0.0,>=1.14.4",
|
||||
"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",
|
||||
"tqdm<5.0.0,>=4.62.2",
|
||||
]
|
||||
files = [
|
||||
{file = "djlint-1.34.0-py3-none-any.whl", hash = "sha256:bdc26cc607dee8b46e262654eb0fbac7862c34d68172c8adc25a0b56fc7d8173"},
|
||||
{file = "djlint-1.34.0.tar.gz", hash = "sha256:60b4f4ca99fd83106603bdd466f35314fda33776f3a6e70ea9d674da9d0ad053"},
|
||||
{file = "djlint-1.34.1-py3-none-any.whl", hash = "sha256:96ff1c464fb6f061130ebc88663a2ea524d7ec51f4b56221a2b3f0320a3cfce8"},
|
||||
{file = "djlint-1.34.1.tar.gz", hash = "sha256:db93fa008d19eaadb0454edf1704931d14469d48508daba2df9941111f408346"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -869,8 +874,8 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "8.18.1"
|
||||
requires_python = ">=3.9"
|
||||
version = "8.19.0"
|
||||
requires_python = ">=3.10"
|
||||
summary = "IPython: Productive Interactive Computing"
|
||||
dependencies = [
|
||||
"colorama; sys_platform == \"win32\"",
|
||||
@ -884,8 +889,8 @@ dependencies = [
|
||||
"traitlets>=5",
|
||||
]
|
||||
files = [
|
||||
{file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"},
|
||||
{file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"},
|
||||
{file = "ipython-8.19.0-py3-none-any.whl", hash = "sha256:2f55d59370f59d0d2b2212109fe0e6035cfea436b1c0e6150ad2244746272ec5"},
|
||||
{file = "ipython-8.19.0.tar.gz", hash = "sha256:ac4da4ecf0042fb4e0ce57c60430c2db3c719fa8bdf92f8631d6bd8a5785d1f0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -924,40 +929,41 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "4.9.3"
|
||||
version = "5.0.0"
|
||||
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."
|
||||
files = [
|
||||
{file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"},
|
||||
{file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"},
|
||||
{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-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"},
|
||||
{file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"},
|
||||
{file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"},
|
||||
{file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"},
|
||||
{file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"},
|
||||
{file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"},
|
||||
{file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"},
|
||||
{file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"},
|
||||
{file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"},
|
||||
{file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"},
|
||||
{file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"},
|
||||
{file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"},
|
||||
{file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"},
|
||||
{file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"},
|
||||
{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-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"},
|
||||
{file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"},
|
||||
{file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"},
|
||||
{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-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"},
|
||||
{file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"},
|
||||
{file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"},
|
||||
{file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"},
|
||||
{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-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"},
|
||||
{file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"},
|
||||
{file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5382612ba2424cea5d2c89e2c29077023d8de88f8d60d5ceff5f76334516df9e"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:07a900735bad9af7be3085480bf384f68ed5580ba465b39a098e6a882c060d6b"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:980ba47c8db4b9d870014c7040edb230825b79017a6a27aa54cdb6fcc02d8cc0"},
|
||||
{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-5.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4a45a278518e4308865c1e9dbb2c42ce84fb154efb03adeb16fdae3c1687c7c9"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:59cea9ba1c675fbd6867ca1078fc717a113e7f5b7644943b74137b7cc55abebf"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd39ef87fd1f7bb5c4aa53454936e6135cbfe03fe3744e8218be193f9e4fef16"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e6bb39d91bf932e7520cb5718ae3c2f498052aca53294d5d59fdd9068fe1a7f2"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-win32.whl", hash = "sha256:21af2c3862db6f4f486cddf73ec1157b40d5828876c47cd880edcbad8240ea1b"},
|
||||
{file = "lxml-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:c1249aa4eaced30b59ecf8b8cae0b1ccede04583c74ca7d10b6f8bbead908b2c"},
|
||||
{file = "lxml-5.0.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f30e697b6215e759d0824768b2c5b0618d2dc19abe6c67eeed2b0460f52470d1"},
|
||||
{file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d1bb64646480c36a4aa1b6a44a5b6e33d0fcbeab9f53f1b39072cd3bb2c6243a"},
|
||||
{file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4e69c36c8618707a90ed3fb6f48a6cc9254ffcdbf7b259e439a5ae5fbf9c5206"},
|
||||
{file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca498f8554a09fbc3a2f8fc4b23261e07bc27bef99b3df98e2570688033f6fc"},
|
||||
{file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0326e9b8176ea77269fb39e7af4010906e73e9496a9f8eaf06d253b1b1231ceb"},
|
||||
{file = "lxml-5.0.0-cp312-cp312-win32.whl", hash = "sha256:5fb988e15378d6e905ca8f60813950a0c56da9469d0e8e5d8fe785b282684ec5"},
|
||||
{file = "lxml-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:bb58e8f4b2cfe012cd312239b8d5139995fe8f5945c7c26d5fbbbb1ddb9acd47"},
|
||||
{file = "lxml-5.0.0-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:96095bfc0c02072fc89afa67626013a253596ea5118b8a7f4daaae049dafa096"},
|
||||
{file = "lxml-5.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:992029258ed719f130d5a9c443d142c32843046f1263f2c492862b2a853be570"},
|
||||
{file = "lxml-5.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:db40e85cffd22f7d65dcce30e85af565a66401a6ed22fc0c56ed342cfa4ffc43"},
|
||||
{file = "lxml-5.0.0-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:cfa8a4cdc3765574b7fd0c7cfa5fbd1e2108014c9dfd299c679e5152bea9a55e"},
|
||||
{file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:049fef98d02513c34f5babd07569fc1cf1ed14c0f2fbff18fe72597f977ef3c2"},
|
||||
{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-5.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:766868f729f3ab84125350f1a0ea2594d8b1628a608a574542a5aff7355b9941"},
|
||||
{file = "lxml-5.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99cad5c912f359e59e921689c04e54662cdd80835d80eeaa931e22612f515df7"},
|
||||
{file = "lxml-5.0.0-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:c90c593aa8dd57d5dab0ef6d7d64af894008971d98e6a41b320fdd75258fbc6e"},
|
||||
{file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8134d5441d1ed6a682e3de3d7a98717a328dce619ee9c4c8b3b91f0cb0eb3e28"},
|
||||
{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-5.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:894c5f71186b410679aaab5774543fcb9cbabe8893f0b31d11cf28a0740e80be"},
|
||||
{file = "lxml-5.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9cd3d6c2c67d4fdcd795e4945e2ba5434909c96640b4cc09453bd0dc7e8e1bac"},
|
||||
{file = "lxml-5.0.0.zip", hash = "sha256:2219cbf790e701acf9a21a31ead75f983e73daf0eceb9da6990212e4d20ebefe"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1110,7 +1116,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.6.1"
|
||||
version = "1.7.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Optional static typing for Python"
|
||||
dependencies = [
|
||||
@ -1118,18 +1124,18 @@ dependencies = [
|
||||
"typing-extensions>=4.1.0",
|
||||
]
|
||||
files = [
|
||||
{file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"},
|
||||
{file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"},
|
||||
{file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"},
|
||||
{file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"},
|
||||
{file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1144,14 +1150,39 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mysqlclient"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Python interface to MySQL"
|
||||
files = [
|
||||
{file = "mysqlclient-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5670679ff1be1cc3fef0fa81bf39f0cd70605ba121141050f02743eb878ac114"},
|
||||
{file = "mysqlclient-2.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:955dba905a7443ce4788c63fdb9f8d688316260cf60b20ff51ac3b1c77616ede"},
|
||||
{file = "mysqlclient-2.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:530ece9995a36cadb6211b9787f0c9e05cdab6702549bdb4236af5e9b535ed6a"},
|
||||
{file = "mysqlclient-2.2.0.tar.gz", hash = "sha256:04368445f9c487d8abb7a878e3d23e923e6072c04a6c320f9e0dc8a82efba14e"},
|
||||
{file = "mysqlclient-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:8f40c872f19639366e3df27bef2ff087be0e3ee0bd3453470bd29f46b54a90f6"},
|
||||
{file = "mysqlclient-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:45600f4f321096bd1ead3355bc62cfcf8d97dc78df94e4ab5db72ecb5db1bd04"},
|
||||
{file = "mysqlclient-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:97eee76818774bb695e018ff4c3dafaab74b9a0b0cf32c90b02caeec3b19cd8e"},
|
||||
{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]]
|
||||
@ -1191,12 +1222,12 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.0"
|
||||
requires_python = ">=3.7"
|
||||
version = "0.12.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Utility library for gitignore style pattern matching of file paths."
|
||||
files = [
|
||||
{file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"},
|
||||
{file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
|
||||
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1535,12 +1566,12 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "69.0.2"
|
||||
version = "69.0.3"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
files = [
|
||||
{file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"},
|
||||
{file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"},
|
||||
{file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
|
||||
{file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1599,15 +1630,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tinycss2"
|
||||
version = "1.2.1"
|
||||
requires_python = ">=3.7"
|
||||
version = "1.1.1"
|
||||
requires_python = ">=3.6"
|
||||
summary = "A tiny CSS parser"
|
||||
dependencies = [
|
||||
"webencodings>=0.4",
|
||||
]
|
||||
files = [
|
||||
{file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"},
|
||||
{file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"},
|
||||
{file = "tinycss2-1.1.1-py3-none-any.whl", hash = "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8"},
|
||||
{file = "tinycss2-1.1.1.tar.gz", hash = "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1847,7 +1878,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "weasyprint"
|
||||
version = "60.1"
|
||||
version = "60.2"
|
||||
requires_python = ">=3.7"
|
||||
summary = "The Awesome Document Factory"
|
||||
dependencies = [
|
||||
@ -1861,8 +1892,8 @@ dependencies = [
|
||||
"tinycss2>=1.0.0",
|
||||
]
|
||||
files = [
|
||||
{file = "weasyprint-60.1-py3-none-any.whl", hash = "sha256:55227e5e44f5f34bc9cec651329bd38d063ef7d29151d4b058d4af1ca943d4a7"},
|
||||
{file = "weasyprint-60.1.tar.gz", hash = "sha256:56b9812280118357b0f63b1efe18199e08343d4a56a3393c1d475ab878cea26a"},
|
||||
{file = "weasyprint-60.2-py3-none-any.whl", hash = "sha256:3e98eedcc1c5a14cb310c293c6d59a479f59a13f0d705ff07106482827fa5705"},
|
||||
{file = "weasyprint-60.2.tar.gz", hash = "sha256:0c0cdd617a78699262b80026e67fa1692e3802cfa966395436eeaf6f787dd126"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7,7 +7,7 @@ authors = [
|
||||
]
|
||||
dependencies = [
|
||||
"django~=5.0",
|
||||
"django-admin-logs~=1.0",
|
||||
"django-admin-logs~=1.1",
|
||||
"django-auth-ldap~=4.6",
|
||||
"django-markdownx~=4.0",
|
||||
"django-recurrence~=1.11",
|
||||
@ -17,18 +17,18 @@ dependencies = [
|
||||
"mdformat~=0.7",
|
||||
"mdformat-tables~=0.4",
|
||||
"mysqlclient~=2.2",
|
||||
"bleach~=6.1",
|
||||
"django-autocomplete-light~=3.9",
|
||||
"weasyprint~=60.1",
|
||||
"weasyprint~=60.2",
|
||||
"requests~=2.31",
|
||||
"semver~=3.0",
|
||||
"djangorestframework~=3.14",
|
||||
"django-q2~=1.6",
|
||||
"lxml~=4.9",
|
||||
"lxml~=5.0",
|
||||
"django-object-actions~=4.2",
|
||||
"udm-rest-client~=1.2",
|
||||
"openapi-client-udm~=1.0",
|
||||
"django-bleach~=1.0",
|
||||
"django-nh3~=0.1",
|
||||
"nh3~=0.2",
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@ -82,11 +82,11 @@ include_packages = ["openapi-client-udm"]
|
||||
|
||||
[tool.pdm.dev-dependencies]
|
||||
lint = [
|
||||
"black~=23.11",
|
||||
"black~=23.12",
|
||||
"djlint~=1.34",
|
||||
]
|
||||
typing = [
|
||||
"mypy~=1.6",
|
||||
"mypy~=1.7",
|
||||
"django-stubs~=4.2",
|
||||
"setuptools~=69.0",
|
||||
"types-bleach~=6.1",
|
||||
@ -100,7 +100,7 @@ debug = [
|
||||
]
|
||||
dev = [
|
||||
"django-extensions~=3.2",
|
||||
"ipython~=8.18",
|
||||
"ipython~=8.19",
|
||||
]
|
||||
|
||||
[tool.pdm.scripts]
|
||||
|
@ -1,4 +1,4 @@
|
||||
import bleach
|
||||
import nh3
|
||||
from markdownx.utils import markdownify
|
||||
|
||||
# fmt: off
|
||||
@ -23,5 +23,5 @@ MARKDOWN_ATTRS = {
|
||||
|
||||
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user