mirror of
https://github.com/ae-utbm/sith.git
synced 2025-04-16 02:50:22 +00:00
Merge pull request #1084 from ae-utbm/taiste
Django 5.2, HTMX for billing infos form, eurocks widget consent message and new promo 24 logo
This commit is contained in:
commit
6377acfffa
@ -1,7 +1,7 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.9.10
|
||||
rev: v0.11.4
|
||||
hooks:
|
||||
- id: ruff # just check the code, and print the errors
|
||||
- id: ruff # actually fix the fixable errors, but print nothing
|
||||
|
@ -1,14 +0,0 @@
|
||||
#
|
||||
# Copyright 2023 © AE UTBM
|
||||
# ae@utbm.fr / ae.info@utbm.fr
|
||||
#
|
||||
# This file is part of the website of the UTBM Student Association (AE UTBM),
|
||||
# https://ae.utbm.fr.
|
||||
#
|
||||
# You can find the source code of the website at https://github.com/ae-utbm/sith
|
||||
#
|
||||
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
|
||||
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
|
||||
# OR WITHIN THE LOCAL FILE "LICENSE"
|
||||
#
|
||||
#
|
@ -1,280 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import counter.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AccountingType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"code",
|
||||
models.CharField(
|
||||
max_length=16,
|
||||
verbose_name="code",
|
||||
validators=[
|
||||
django.core.validators.RegexValidator(
|
||||
"^[0-9]*$",
|
||||
"An accounting type code contains only numbers",
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
("label", models.CharField(max_length=128, verbose_name="label")),
|
||||
(
|
||||
"movement_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("CREDIT", "Credit"),
|
||||
("DEBIT", "Debit"),
|
||||
("NEUTRAL", "Neutral"),
|
||||
],
|
||||
max_length=12,
|
||||
verbose_name="movement type",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "accounting type",
|
||||
"ordering": ["movement_type", "code"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="BankAccount",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=30, verbose_name="name")),
|
||||
(
|
||||
"iban",
|
||||
models.CharField(max_length=255, blank=True, verbose_name="iban"),
|
||||
),
|
||||
(
|
||||
"number",
|
||||
models.CharField(
|
||||
max_length=255, blank=True, verbose_name="account number"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "Bank account", "ordering": ["club", "name"]},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ClubAccount",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=30, verbose_name="name")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Club account",
|
||||
"ordering": ["bank_account", "name"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Company",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=60, verbose_name="name")),
|
||||
],
|
||||
options={"verbose_name": "company"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="GeneralJournal",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
),
|
||||
),
|
||||
("start_date", models.DateField(verbose_name="start date")),
|
||||
(
|
||||
"end_date",
|
||||
models.DateField(
|
||||
null=True, verbose_name="end date", default=None, blank=True
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=40, verbose_name="name")),
|
||||
(
|
||||
"closed",
|
||||
models.BooleanField(verbose_name="is closed", default=False),
|
||||
),
|
||||
(
|
||||
"amount",
|
||||
counter.fields.CurrencyField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
verbose_name="amount",
|
||||
max_digits=12,
|
||||
),
|
||||
),
|
||||
(
|
||||
"effective_amount",
|
||||
counter.fields.CurrencyField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
verbose_name="effective_amount",
|
||||
max_digits=12,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "General journal", "ordering": ["-start_date"]},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Operation",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
),
|
||||
),
|
||||
("number", models.IntegerField(verbose_name="number")),
|
||||
(
|
||||
"amount",
|
||||
counter.fields.CurrencyField(
|
||||
decimal_places=2, max_digits=12, verbose_name="amount"
|
||||
),
|
||||
),
|
||||
("date", models.DateField(verbose_name="date")),
|
||||
("remark", models.CharField(max_length=128, verbose_name="comment")),
|
||||
(
|
||||
"mode",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("CHECK", "Check"),
|
||||
("CASH", "Cash"),
|
||||
("TRANSFERT", "Transfert"),
|
||||
("CARD", "Credit card"),
|
||||
],
|
||||
max_length=255,
|
||||
verbose_name="payment method",
|
||||
),
|
||||
),
|
||||
(
|
||||
"cheque_number",
|
||||
models.CharField(
|
||||
max_length=32,
|
||||
null=True,
|
||||
verbose_name="cheque number",
|
||||
default="",
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
("done", models.BooleanField(verbose_name="is done", default=False)),
|
||||
(
|
||||
"target_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("USER", "User"),
|
||||
("CLUB", "Club"),
|
||||
("ACCOUNT", "Account"),
|
||||
("COMPANY", "Company"),
|
||||
("OTHER", "Other"),
|
||||
],
|
||||
max_length=10,
|
||||
verbose_name="target type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"target_id",
|
||||
models.IntegerField(
|
||||
null=True, verbose_name="target id", blank=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"target_label",
|
||||
models.CharField(
|
||||
max_length=32,
|
||||
blank=True,
|
||||
verbose_name="target label",
|
||||
default="",
|
||||
),
|
||||
),
|
||||
(
|
||||
"accounting_type",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
related_name="operations",
|
||||
verbose_name="accounting type",
|
||||
to="accounting.AccountingType",
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"ordering": ["-number"]},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SimplifiedAccountingType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
),
|
||||
),
|
||||
("label", models.CharField(max_length=128, verbose_name="label")),
|
||||
(
|
||||
"accounting_type",
|
||||
models.ForeignKey(
|
||||
verbose_name="simplified accounting types",
|
||||
to="accounting.AccountingType",
|
||||
related_name="simplified_types",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "simplified type",
|
||||
"ordering": ["accounting_type__movement_type", "accounting_type__code"],
|
||||
},
|
||||
),
|
||||
]
|
@ -1,105 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("club", "0001_initial"),
|
||||
("accounting", "0001_initial"),
|
||||
("core", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="operation",
|
||||
name="invoice",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
related_name="operations",
|
||||
verbose_name="invoice",
|
||||
to="core.SithFile",
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="operation",
|
||||
name="journal",
|
||||
field=models.ForeignKey(
|
||||
verbose_name="journal",
|
||||
to="accounting.GeneralJournal",
|
||||
related_name="operations",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="operation",
|
||||
name="linked_operation",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
blank=True,
|
||||
to="accounting.Operation",
|
||||
null=True,
|
||||
related_name="operation_linked_to",
|
||||
verbose_name="linked operation",
|
||||
default=None,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="operation",
|
||||
name="simpleaccounting_type",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
related_name="operations",
|
||||
verbose_name="simple type",
|
||||
to="accounting.SimplifiedAccountingType",
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="generaljournal",
|
||||
name="club_account",
|
||||
field=models.ForeignKey(
|
||||
verbose_name="club account",
|
||||
to="accounting.ClubAccount",
|
||||
related_name="journals",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="clubaccount",
|
||||
name="bank_account",
|
||||
field=models.ForeignKey(
|
||||
verbose_name="bank account",
|
||||
to="accounting.BankAccount",
|
||||
related_name="club_accounts",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="clubaccount",
|
||||
name="club",
|
||||
field=models.ForeignKey(
|
||||
verbose_name="club",
|
||||
to="club.Club",
|
||||
related_name="club_account",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="bankaccount",
|
||||
name="club",
|
||||
field=models.ForeignKey(
|
||||
verbose_name="club",
|
||||
to="club.Club",
|
||||
related_name="bank_accounts",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="operation", unique_together={("number", "journal")}
|
||||
),
|
||||
]
|
@ -1,48 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import phonenumber_field.modelfields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("accounting", "0002_auto_20160824_2152")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="city",
|
||||
field=models.CharField(blank=True, verbose_name="city", max_length=60),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="country",
|
||||
field=models.CharField(blank=True, verbose_name="country", max_length=32),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="email",
|
||||
field=models.EmailField(blank=True, verbose_name="email", max_length=254),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="phone",
|
||||
field=phonenumber_field.modelfields.PhoneNumberField(
|
||||
blank=True, verbose_name="phone", max_length=128
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="postcode",
|
||||
field=models.CharField(blank=True, verbose_name="postcode", max_length=10),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="street",
|
||||
field=models.CharField(blank=True, verbose_name="street", max_length=60),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="company",
|
||||
name="website",
|
||||
field=models.CharField(blank=True, verbose_name="website", max_length=64),
|
||||
),
|
||||
]
|
@ -1,50 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("accounting", "0003_auto_20160824_2203")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Label",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
primary_key=True,
|
||||
auto_created=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=64, verbose_name="label")),
|
||||
(
|
||||
"club_account",
|
||||
models.ForeignKey(
|
||||
related_name="labels",
|
||||
verbose_name="club account",
|
||||
to="accounting.ClubAccount",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="operation",
|
||||
name="label",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="operations",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="label",
|
||||
to="accounting.Label",
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="label", unique_together={("name", "club_account")}
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("accounting", "0004_auto_20161005_1505")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="operation",
|
||||
name="remark",
|
||||
field=models.CharField(
|
||||
null=True, max_length=128, blank=True, verbose_name="comment"
|
||||
),
|
||||
)
|
||||
]
|
@ -1,34 +0,0 @@
|
||||
# Generated by Django 4.2.20 on 2025-03-14 16:06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("accounting", "0005_auto_20170324_0917")]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(model_name="bankaccount", name="club"),
|
||||
migrations.RemoveField(model_name="clubaccount", name="bank_account"),
|
||||
migrations.RemoveField(model_name="clubaccount", name="club"),
|
||||
migrations.DeleteModel(name="Company"),
|
||||
migrations.RemoveField(model_name="generaljournal", name="club_account"),
|
||||
migrations.AlterUniqueTogether(name="label", unique_together=None),
|
||||
migrations.RemoveField(model_name="label", name="club_account"),
|
||||
migrations.AlterUniqueTogether(name="operation", unique_together=None),
|
||||
migrations.RemoveField(model_name="operation", name="accounting_type"),
|
||||
migrations.RemoveField(model_name="operation", name="invoice"),
|
||||
migrations.RemoveField(model_name="operation", name="journal"),
|
||||
migrations.RemoveField(model_name="operation", name="label"),
|
||||
migrations.RemoveField(model_name="operation", name="linked_operation"),
|
||||
migrations.RemoveField(model_name="operation", name="simpleaccounting_type"),
|
||||
migrations.RemoveField(
|
||||
model_name="simplifiedaccountingtype", name="accounting_type"
|
||||
),
|
||||
migrations.DeleteModel(name="AccountingType"),
|
||||
migrations.DeleteModel(name="BankAccount"),
|
||||
migrations.DeleteModel(name="ClubAccount"),
|
||||
migrations.DeleteModel(name="GeneralJournal"),
|
||||
migrations.DeleteModel(name="Label"),
|
||||
migrations.DeleteModel(name="Operation"),
|
||||
migrations.DeleteModel(name="SimplifiedAccountingType"),
|
||||
]
|
@ -1,14 +0,0 @@
|
||||
#
|
||||
# Copyright 2023 © AE UTBM
|
||||
# ae@utbm.fr / ae.info@utbm.fr
|
||||
#
|
||||
# This file is part of the website of the UTBM Student Association (AE UTBM),
|
||||
# https://ae.utbm.fr.
|
||||
#
|
||||
# You can find the source code of the website at https://github.com/ae-utbm/sith
|
||||
#
|
||||
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
|
||||
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
|
||||
# OR WITHIN THE LOCAL FILE "LICENSE"
|
||||
#
|
||||
#
|
@ -1,5 +1,3 @@
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.core.validators import EmailValidator
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -7,12 +5,18 @@ from django.utils.translation import gettext_lazy as _
|
||||
from antispam.models import ToxicDomain
|
||||
|
||||
|
||||
class AntiSpamEmailValidator(EmailValidator):
|
||||
def __call__(self, value: str):
|
||||
super().__call__(value)
|
||||
domain_part = value.rsplit("@", 1)[1]
|
||||
if ToxicDomain.objects.filter(domain=domain_part).exists():
|
||||
raise forms.ValidationError(_("Email domain is not allowed."))
|
||||
|
||||
|
||||
validate_antispam_email = AntiSpamEmailValidator()
|
||||
|
||||
|
||||
class AntiSpamEmailField(forms.EmailField):
|
||||
"""An email field that email addresses with a known toxic domain."""
|
||||
|
||||
def run_validators(self, value: str):
|
||||
super().run_validators(value)
|
||||
# Domain part should exist since email validation is guaranteed to run first
|
||||
domain = re.search(EmailValidator.domain_regex, value)
|
||||
if ToxicDomain.objects.filter(domain=domain[0]).exists():
|
||||
raise forms.ValidationError(_("Email domain is not allowed."))
|
||||
default_validators = [validate_antispam_email]
|
||||
|
@ -34,7 +34,7 @@ class Command(BaseCommand):
|
||||
f"Source {provider} responded with code {res.status_code}"
|
||||
)
|
||||
continue
|
||||
domains |= set(res.content.decode().splitlines())
|
||||
domains |= set(res.text.splitlines())
|
||||
return domains
|
||||
|
||||
def _update_domains(self, domains: set[str]):
|
||||
|
@ -196,9 +196,7 @@ class ClubMemberForm(forms.Form):
|
||||
self.request_user = kwargs.pop("request_user")
|
||||
self.club_members = kwargs.pop("club_members", None)
|
||||
if not self.club_members:
|
||||
self.club_members = (
|
||||
self.club.members.filter(end_date=None).order_by("-role").all()
|
||||
)
|
||||
self.club_members = self.club.members.ongoing().order_by("-role").all()
|
||||
self.request_user_membership = self.club.get_membership_for(self.request_user)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
{{ select_all_checkbox("users_old") }}
|
||||
<p></p>
|
||||
{% endif %}
|
||||
<table>
|
||||
<table id="club_members_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{% trans %}User{% endtrans %}</td>
|
||||
|
@ -6,8 +6,10 @@ from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from model_bakery import baker
|
||||
from model_bakery.recipe import Recipe
|
||||
|
||||
from club.models import Club, Membership
|
||||
from core.baker_recipes import old_subscriber_user, subscriber_user
|
||||
from core.models import User
|
||||
|
||||
|
||||
@ -17,8 +19,9 @@ class TestClub(TestCase):
|
||||
The generated dataset is the one created by the populate command,
|
||||
plus the following modifications :
|
||||
|
||||
- `self.club` is a dummy club recreated for each test
|
||||
- `self.club` has two board members : skia (role 3) and comptable (role 10)
|
||||
- `self.club` is a dummy club
|
||||
- `self.club` has two board members :
|
||||
simple_board_member (role 3) and president (role 10)
|
||||
- `self.club` has one regular member : richard
|
||||
- `self.club` has one former member : sli (who had role 2)
|
||||
- None of the `self.club` members are in the AE club.
|
||||
@ -27,44 +30,30 @@ class TestClub(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
# subscribed users - initial members
|
||||
cls.skia = User.objects.get(username="skia")
|
||||
# by default, Skia is in the AE, which creates side effect
|
||||
cls.skia.memberships.all().delete()
|
||||
cls.president, cls.simple_board_member = subscriber_user.make(_quantity=2)
|
||||
cls.richard = User.objects.get(username="rbatsbak")
|
||||
cls.comptable = User.objects.get(username="comptable")
|
||||
cls.sli = User.objects.get(username="sli")
|
||||
cls.root = User.objects.get(username="root")
|
||||
cls.root = baker.make(User, is_superuser=True)
|
||||
cls.old_subscriber = old_subscriber_user.make()
|
||||
cls.public = baker.make(User)
|
||||
|
||||
# subscribed users - not initial members
|
||||
# subscribed users - not initial member
|
||||
cls.krophil = User.objects.get(username="krophil")
|
||||
cls.subscriber = User.objects.get(username="subscriber")
|
||||
|
||||
# old subscriber
|
||||
cls.old_subscriber = User.objects.get(username="old_subscriber")
|
||||
|
||||
# not subscribed
|
||||
cls.public = User.objects.get(username="public")
|
||||
cls.subscriber = subscriber_user.make()
|
||||
|
||||
cls.ae = Club.objects.get(pk=settings.SITH_MAIN_CLUB_ID)
|
||||
cls.club = baker.make(Club)
|
||||
cls.members_url = reverse("club:club_members", kwargs={"club_id": cls.club.id})
|
||||
a_month_ago = now() - timedelta(days=30)
|
||||
yesterday = now() - timedelta(days=1)
|
||||
Membership.objects.create(
|
||||
club=cls.club, user=cls.skia, start_date=a_month_ago, role=3
|
||||
membership_recipe = Recipe(Membership, club=cls.club)
|
||||
membership_recipe.make(
|
||||
user=cls.simple_board_member, start_date=a_month_ago, role=3
|
||||
)
|
||||
Membership.objects.create(club=cls.club, user=cls.richard, role=1)
|
||||
Membership.objects.create(
|
||||
club=cls.club, user=cls.comptable, start_date=a_month_ago, role=10
|
||||
)
|
||||
|
||||
# sli was a member but isn't anymore
|
||||
Membership.objects.create(
|
||||
club=cls.club,
|
||||
user=cls.sli,
|
||||
start_date=a_month_ago,
|
||||
end_date=yesterday,
|
||||
role=2,
|
||||
membership_recipe.make(user=cls.richard, role=1)
|
||||
membership_recipe.make(user=cls.president, start_date=a_month_ago, role=10)
|
||||
membership_recipe.make( # sli was a member but isn't anymore
|
||||
user=cls.sli, start_date=a_month_ago, end_date=yesterday, role=2
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
|
@ -38,7 +38,7 @@ class TestMailingForm(TestCase):
|
||||
self.assertRedirects(response, self.mail_url)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
assert "Liste de diffusion foyer@utbm.fr" in response.content.decode()
|
||||
assert "Liste de diffusion foyer@utbm.fr" in response.text
|
||||
|
||||
# Test with Root
|
||||
self.client.force_login(self.root)
|
||||
@ -48,7 +48,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
assert "Liste de diffusion mde@utbm.fr" in response.content.decode()
|
||||
assert "Liste de diffusion mde@utbm.fr" in response.text
|
||||
|
||||
def test_mailing_list_add_moderation(self):
|
||||
self.client.force_login(self.rbatsbak)
|
||||
@ -58,7 +58,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
content = response.text
|
||||
assert "Liste de diffusion mde@utbm.fr" not in content
|
||||
assert "<p>Listes de diffusions en attente de modération</p>" in content
|
||||
assert "<li>mde@utbm.fr" in content
|
||||
@ -90,7 +90,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
assert "skia@git.an" not in response.content.decode()
|
||||
assert "skia@git.an" not in response.text
|
||||
|
||||
def test_add_new_subscription_success(self):
|
||||
# Prepare mailing list
|
||||
@ -111,7 +111,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
assert "skia@git.an" in response.content.decode()
|
||||
assert "skia@git.an" in response.text
|
||||
|
||||
# Add multiple users
|
||||
self.client.post(
|
||||
@ -124,7 +124,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
content = response.text
|
||||
assert "richard@git.an" in content
|
||||
assert "comunity@git.an" in content
|
||||
assert "skia@git.an" in content
|
||||
@ -140,7 +140,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
content = response.text
|
||||
assert "richard@git.an" in content
|
||||
assert "comunity@git.an" in content
|
||||
assert "skia@git.an" in content
|
||||
@ -158,7 +158,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
content = response.text
|
||||
assert "richard@git.an" in content
|
||||
assert "comunity@git.an" in content
|
||||
assert "skia@git.an" in content
|
||||
@ -185,7 +185,7 @@ class TestMailingForm(TestCase):
|
||||
assert response.status_code
|
||||
self.assertInHTML(
|
||||
_("You must specify at least an user or an email address"),
|
||||
response.content.decode(),
|
||||
response.text,
|
||||
)
|
||||
|
||||
# No mailing specified
|
||||
@ -197,7 +197,7 @@ class TestMailingForm(TestCase):
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert _("This field is required") in response.content.decode()
|
||||
assert _("This field is required") in response.text
|
||||
|
||||
# One of the selected users doesn't exist
|
||||
response = self.client.post(
|
||||
@ -211,7 +211,7 @@ class TestMailingForm(TestCase):
|
||||
assert response.status_code == 200
|
||||
self.assertInHTML(
|
||||
_("You must specify at least an user or an email address"),
|
||||
response.content.decode(),
|
||||
response.text,
|
||||
)
|
||||
|
||||
# An user has no email address
|
||||
@ -229,7 +229,7 @@ class TestMailingForm(TestCase):
|
||||
assert response.status_code == 200
|
||||
self.assertInHTML(
|
||||
_("One of the selected users doesn't have an email address"),
|
||||
response.content.decode(),
|
||||
response.text,
|
||||
)
|
||||
|
||||
self.krophil.email = "krophil@git.an"
|
||||
@ -257,7 +257,7 @@ class TestMailingForm(TestCase):
|
||||
assert response.status_code == 200
|
||||
self.assertInHTML(
|
||||
_("This email is already suscribed in this mailing"),
|
||||
response.content.decode(),
|
||||
response.text,
|
||||
)
|
||||
|
||||
def test_remove_subscription_success(self):
|
||||
@ -283,7 +283,7 @@ class TestMailingForm(TestCase):
|
||||
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
content = response.text
|
||||
|
||||
assert "comunity@git.an" in content
|
||||
assert "richard@git.an" in content
|
||||
@ -299,7 +299,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
content = response.text
|
||||
|
||||
assert "comunity@git.an" in content
|
||||
assert "richard@git.an" in content
|
||||
@ -320,7 +320,7 @@ class TestMailingForm(TestCase):
|
||||
)
|
||||
response = self.client.get(self.mail_url)
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
content = response.text
|
||||
|
||||
assert "comunity@git.an" not in content
|
||||
assert "richard@git.an" not in content
|
||||
|
@ -1,9 +1,12 @@
|
||||
from bs4 import BeautifulSoup
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Max
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import localdate, localtime, now
|
||||
from model_bakery import baker
|
||||
|
||||
from club.forms import ClubMemberForm
|
||||
from club.models import Membership
|
||||
from club.tests.base import TestClub
|
||||
from core.baker_recipes import subscriber_user
|
||||
@ -17,8 +20,8 @@ class TestMembershipQuerySet(TestClub):
|
||||
"""
|
||||
current_members = list(self.club.members.ongoing().order_by("id"))
|
||||
expected = [
|
||||
self.skia.memberships.get(club=self.club),
|
||||
self.comptable.memberships.get(club=self.club),
|
||||
self.simple_board_member.memberships.get(club=self.club),
|
||||
self.president.memberships.get(club=self.club),
|
||||
self.richard.memberships.get(club=self.club),
|
||||
]
|
||||
expected.sort(key=lambda i: i.id)
|
||||
@ -30,8 +33,8 @@ class TestMembershipQuerySet(TestClub):
|
||||
self.richard.memberships.filter(club=self.club).update(end_date=today)
|
||||
current_members = list(self.club.members.ongoing().order_by("id"))
|
||||
expected = [
|
||||
self.skia.memberships.get(club=self.club),
|
||||
self.comptable.memberships.get(club=self.club),
|
||||
self.simple_board_member.memberships.get(club=self.club),
|
||||
self.president.memberships.get(club=self.club),
|
||||
]
|
||||
expected.sort(key=lambda i: i.id)
|
||||
assert current_members == expected
|
||||
@ -42,8 +45,8 @@ class TestMembershipQuerySet(TestClub):
|
||||
"""
|
||||
board_members = list(self.club.members.board().order_by("id"))
|
||||
expected = [
|
||||
self.skia.memberships.get(club=self.club),
|
||||
self.comptable.memberships.get(club=self.club),
|
||||
self.simple_board_member.memberships.get(club=self.club),
|
||||
self.president.memberships.get(club=self.club),
|
||||
# sli is no more member, but he was in the board
|
||||
self.sli.memberships.get(club=self.club),
|
||||
]
|
||||
@ -56,17 +59,17 @@ class TestMembershipQuerySet(TestClub):
|
||||
"""
|
||||
members = list(self.club.members.ongoing().board().order_by("id"))
|
||||
expected = [
|
||||
self.skia.memberships.get(club=self.club),
|
||||
self.comptable.memberships.get(club=self.club),
|
||||
self.simple_board_member.memberships.get(club=self.club),
|
||||
self.president.memberships.get(club=self.club),
|
||||
]
|
||||
expected.sort(key=lambda i: i.id)
|
||||
assert members == expected
|
||||
|
||||
def test_update_invalidate_cache(self):
|
||||
"""Test that the `update` queryset method properly invalidate cache."""
|
||||
mem_skia = self.skia.memberships.get(club=self.club)
|
||||
mem_skia = self.simple_board_member.memberships.get(club=self.club)
|
||||
cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia)
|
||||
self.skia.memberships.update(end_date=localtime(now()).date())
|
||||
self.simple_board_member.memberships.update(end_date=localtime(now()).date())
|
||||
assert (
|
||||
cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}")
|
||||
== "not_member"
|
||||
@ -104,14 +107,14 @@ class TestMembershipQuerySet(TestClub):
|
||||
|
||||
def test_delete_invalidate_cache(self):
|
||||
"""Test that the `delete` queryset properly invalidate cache."""
|
||||
mem_skia = self.skia.memberships.get(club=self.club)
|
||||
mem_comptable = self.comptable.memberships.get(club=self.club)
|
||||
mem_skia = self.simple_board_member.memberships.get(club=self.club)
|
||||
mem_comptable = self.president.memberships.get(club=self.club)
|
||||
cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia)
|
||||
cache.set(
|
||||
f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}", mem_comptable
|
||||
)
|
||||
|
||||
# should delete the subscriptions of skia and comptable
|
||||
# should delete the subscriptions of simple_board_member and president
|
||||
self.club.members.ongoing().board().delete()
|
||||
|
||||
for membership in (mem_skia, mem_comptable):
|
||||
@ -167,36 +170,43 @@ class TestMembership(TestClub):
|
||||
"""Test that a GET request return a page where the requested
|
||||
information are displayed.
|
||||
"""
|
||||
self.client.force_login(self.skia)
|
||||
self.client.force_login(self.simple_board_member)
|
||||
response = self.client.get(self.members_url)
|
||||
assert response.status_code == 200
|
||||
expected_html = (
|
||||
"<table><thead><tr>"
|
||||
"<td>Utilisateur</td><td>Rôle</td><td>Description</td>"
|
||||
"<td>Depuis</td><td>Marquer comme ancien</td>"
|
||||
"</tr></thead><tbody>"
|
||||
)
|
||||
soup = BeautifulSoup(response.text, "lxml")
|
||||
table = soup.find("table", id="club_members_table")
|
||||
assert [r.text for r in table.find("thead").find_all("td")] == [
|
||||
"Utilisateur",
|
||||
"Rôle",
|
||||
"Description",
|
||||
"Depuis",
|
||||
"Marquer comme ancien",
|
||||
]
|
||||
rows = table.find("tbody").find_all("tr")
|
||||
memberships = self.club.members.ongoing().order_by("-role")
|
||||
input_id = 0
|
||||
for membership in memberships.select_related("user"):
|
||||
for row, membership in zip(
|
||||
rows, memberships.select_related("user"), strict=False
|
||||
):
|
||||
user = membership.user
|
||||
expected_html += (
|
||||
f'<tr><td><a href="{reverse("core:user_profile", args=[user.id])}">'
|
||||
f"{user.get_display_name()}</a></td>"
|
||||
f"<td>{settings.SITH_CLUB_ROLES[membership.role]}</td>"
|
||||
f"<td>{membership.description}</td>"
|
||||
f"<td>{membership.start_date}</td><td>"
|
||||
)
|
||||
if membership.role <= 3: # 3 is the role of skia
|
||||
expected_html += (
|
||||
'<input type="checkbox" name="users_old" '
|
||||
f'value="{user.id}" '
|
||||
f'id="id_users_old_{input_id}">'
|
||||
)
|
||||
input_id += 1
|
||||
expected_html += "</td></tr>"
|
||||
expected_html += "</tbody></table>"
|
||||
self.assertInHTML(expected_html, response.content.decode())
|
||||
user_url = reverse("core:user_profile", args=[user.id])
|
||||
cols = row.find_all("td")
|
||||
user_link = cols[0].find("a")
|
||||
assert user_link.attrs["href"] == user_url
|
||||
assert user_link.text == user.get_display_name()
|
||||
assert cols[1].text == settings.SITH_CLUB_ROLES[membership.role]
|
||||
assert cols[2].text == membership.description
|
||||
assert cols[3].text == str(membership.start_date)
|
||||
|
||||
if membership.role <= 3: # 3 is the role of simple_board_member
|
||||
form_input = cols[4].find("input")
|
||||
expected_attrs = {
|
||||
"type": "checkbox",
|
||||
"name": "users_old",
|
||||
"value": str(user.id),
|
||||
}
|
||||
assert form_input.attrs.items() >= expected_attrs.items()
|
||||
else:
|
||||
assert cols[4].find_all() == []
|
||||
|
||||
def test_root_add_one_club_member(self):
|
||||
"""Test that root users can add members to clubs, one at a time."""
|
||||
@ -228,64 +238,61 @@ class TestMembership(TestClub):
|
||||
"""Test that users who are not currently subscribed
|
||||
cannot be members of clubs.
|
||||
"""
|
||||
self.client.force_login(self.root)
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users": self.public.id, "role": 1},
|
||||
for user in self.public, self.old_subscriber:
|
||||
form = ClubMemberForm(
|
||||
data={"users": [user.id], "role": 1},
|
||||
request_user=self.root,
|
||||
club=self.club,
|
||||
)
|
||||
assert not self.public.memberships.filter(club=self.club).exists()
|
||||
assert '<ul class="errorlist"><li>' in response.content.decode()
|
||||
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users": self.old_subscriber.id, "role": 1},
|
||||
)
|
||||
assert not self.public.memberships.filter(club=self.club).exists()
|
||||
assert self.club.get_membership_for(self.public) is None
|
||||
assert '<ul class="errorlist"><li>' in response.content.decode()
|
||||
assert not form.is_valid()
|
||||
assert form.errors == {
|
||||
"users": [
|
||||
"L'utilisateur doit être cotisant pour faire partie d'un club"
|
||||
]
|
||||
}
|
||||
|
||||
def test_add_members_already_members(self):
|
||||
"""Test that users who are already members of a club
|
||||
cannot be added again to this club.
|
||||
"""
|
||||
self.client.force_login(self.root)
|
||||
current_membership = self.skia.memberships.ongoing().get(club=self.club)
|
||||
nb_memberships = self.skia.memberships.count()
|
||||
current_membership = self.simple_board_member.memberships.ongoing().get(
|
||||
club=self.club
|
||||
)
|
||||
nb_memberships = self.simple_board_member.memberships.count()
|
||||
self.client.post(
|
||||
self.members_url,
|
||||
{"users": self.skia.id, "role": current_membership.role + 1},
|
||||
{"users": self.simple_board_member.id, "role": current_membership.role + 1},
|
||||
)
|
||||
self.simple_board_member.refresh_from_db()
|
||||
assert nb_memberships == self.simple_board_member.memberships.count()
|
||||
new_membership = self.simple_board_member.memberships.ongoing().get(
|
||||
club=self.club
|
||||
)
|
||||
self.skia.refresh_from_db()
|
||||
assert nb_memberships == self.skia.memberships.count()
|
||||
new_membership = self.skia.memberships.ongoing().get(club=self.club)
|
||||
assert current_membership == new_membership
|
||||
assert self.club.get_membership_for(self.skia) == new_membership
|
||||
assert self.club.get_membership_for(self.simple_board_member) == new_membership
|
||||
|
||||
def test_add_not_existing_users(self):
|
||||
"""Test that not existing users cannot be added in clubs.
|
||||
If one user in the request is invalid, no membership creation at all
|
||||
can take place.
|
||||
"""
|
||||
self.client.force_login(self.root)
|
||||
nb_memberships = self.club.members.count()
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users": [9999], "role": 1},
|
||||
max_id = User.objects.aggregate(id=Max("id"))["id"]
|
||||
for members in [max_id + 1], [max_id + 1, self.subscriber.id]:
|
||||
form = ClubMemberForm(
|
||||
data={"users": members, "role": 1},
|
||||
request_user=self.root,
|
||||
club=self.club,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert '<ul class="errorlist"><li>' in response.content.decode()
|
||||
self.club.refresh_from_db()
|
||||
assert self.club.members.count() == nb_memberships
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{
|
||||
"users": (self.subscriber.id, 9999),
|
||||
"start_date": "12/06/2016",
|
||||
"role": 3,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert '<ul class="errorlist"><li>' in response.content.decode()
|
||||
assert not form.is_valid()
|
||||
assert form.errors == {
|
||||
"users": [
|
||||
"Sélectionnez un choix valide. "
|
||||
f"{max_id + 1} n\u2019en fait pas partie."
|
||||
]
|
||||
}
|
||||
self.club.refresh_from_db()
|
||||
assert self.club.members.count() == nb_memberships
|
||||
|
||||
@ -310,17 +317,17 @@ class TestMembership(TestClub):
|
||||
"""Test that a member of the club member cannot create
|
||||
a membership with a greater role than its own.
|
||||
"""
|
||||
self.client.force_login(self.skia)
|
||||
form = ClubMemberForm(
|
||||
data={"users": [self.subscriber.id], "role": 10},
|
||||
request_user=self.simple_board_member,
|
||||
club=self.club,
|
||||
)
|
||||
nb_memberships = self.club.members.count()
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users": self.subscriber.id, "role": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
self.assertInHTML(
|
||||
"<li>Vous n'avez pas la permission de faire cela</li>",
|
||||
response.content.decode(),
|
||||
)
|
||||
|
||||
assert not form.is_valid()
|
||||
assert form.errors == {
|
||||
"__all__": ["Vous n'avez pas la permission de faire cela"]
|
||||
}
|
||||
self.club.refresh_from_db()
|
||||
assert nb_memberships == self.club.members.count()
|
||||
assert not self.subscriber.memberships.filter(club=self.club).exists()
|
||||
@ -328,31 +335,31 @@ class TestMembership(TestClub):
|
||||
def test_add_member_without_role(self):
|
||||
"""Test that trying to add members without specifying their role fails."""
|
||||
self.client.force_login(self.root)
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users": self.subscriber.id, "start_date": "12/06/2016"},
|
||||
)
|
||||
assert (
|
||||
'<ul class="errorlist"><li>Vous devez choisir un r'
|
||||
in response.content.decode()
|
||||
form = ClubMemberForm(
|
||||
data={"users": [self.subscriber.id]},
|
||||
request_user=self.simple_board_member,
|
||||
club=self.club,
|
||||
)
|
||||
|
||||
assert not form.is_valid()
|
||||
assert form.errors == {"role": ["Vous devez choisir un rôle"]}
|
||||
|
||||
def test_end_membership_self(self):
|
||||
"""Test that a member can end its own membership."""
|
||||
self.client.force_login(self.skia)
|
||||
self.client.force_login(self.simple_board_member)
|
||||
self.client.post(
|
||||
self.members_url,
|
||||
{"users_old": self.skia.id},
|
||||
{"users_old": self.simple_board_member.id},
|
||||
)
|
||||
self.skia.refresh_from_db()
|
||||
self.assert_membership_ended_today(self.skia)
|
||||
self.simple_board_member.refresh_from_db()
|
||||
self.assert_membership_ended_today(self.simple_board_member)
|
||||
|
||||
def test_end_membership_lower_role(self):
|
||||
"""Test that board members of the club can end memberships
|
||||
of users with lower roles.
|
||||
"""
|
||||
# remainder : skia has role 3, comptable has role 10, richard has role 1
|
||||
self.client.force_login(self.skia)
|
||||
# remainder : simple_board_member has role 3, president has role 10, richard has role 1
|
||||
self.client.force_login(self.simple_board_member)
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users_old": self.richard.id},
|
||||
@ -365,18 +372,18 @@ class TestMembership(TestClub):
|
||||
"""Test that board members of the club cannot end memberships
|
||||
of users with higher roles.
|
||||
"""
|
||||
membership = self.comptable.memberships.filter(club=self.club).first()
|
||||
self.client.force_login(self.skia)
|
||||
membership = self.president.memberships.filter(club=self.club).first()
|
||||
self.client.force_login(self.simple_board_member)
|
||||
self.client.post(
|
||||
self.members_url,
|
||||
{"users_old": self.comptable.id},
|
||||
{"users_old": self.president.id},
|
||||
)
|
||||
self.club.refresh_from_db()
|
||||
new_membership = self.club.get_membership_for(self.comptable)
|
||||
new_membership = self.club.get_membership_for(self.president)
|
||||
assert new_membership is not None
|
||||
assert new_membership == membership
|
||||
|
||||
membership = self.comptable.memberships.filter(club=self.club).first()
|
||||
membership = self.president.memberships.filter(club=self.club).first()
|
||||
assert membership.end_date is None
|
||||
|
||||
def test_end_membership_as_main_club_board(self):
|
||||
@ -391,10 +398,10 @@ class TestMembership(TestClub):
|
||||
self.client.force_login(subscriber)
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users_old": self.comptable.id},
|
||||
{"users_old": self.president.id},
|
||||
)
|
||||
self.assertRedirects(response, self.members_url)
|
||||
self.assert_membership_ended_today(self.comptable)
|
||||
self.assert_membership_ended_today(self.president)
|
||||
assert self.club.members.ongoing().count() == nb_memberships - 1
|
||||
|
||||
def test_end_membership_as_root(self):
|
||||
@ -403,10 +410,10 @@ class TestMembership(TestClub):
|
||||
self.client.force_login(self.root)
|
||||
response = self.client.post(
|
||||
self.members_url,
|
||||
{"users_old": [self.comptable.id]},
|
||||
{"users_old": [self.president.id]},
|
||||
)
|
||||
self.assertRedirects(response, self.members_url)
|
||||
self.assert_membership_ended_today(self.comptable)
|
||||
self.assert_membership_ended_today(self.president)
|
||||
assert self.club.members.ongoing().count() == nb_memberships - 1
|
||||
|
||||
def test_end_membership_as_foreigner(self):
|
||||
|
@ -20,7 +20,7 @@ def test_page_display_on_club_main_page(client: Client):
|
||||
res = client.get(reverse("club:club_view", kwargs={"club_id": club.id}))
|
||||
|
||||
assert res.status_code == 200
|
||||
soup = BeautifulSoup(res.content.decode(), "lxml")
|
||||
soup = BeautifulSoup(res.text, "lxml")
|
||||
detail_html = soup.find(id="club_detail").find(class_="markdown")
|
||||
assertHTMLEqual(detail_html.decode_contents(), markdown(content))
|
||||
|
||||
@ -34,6 +34,6 @@ def test_club_main_page_without_content(client: Client):
|
||||
res = client.get(reverse("club:club_view", kwargs={"club_id": club.id}))
|
||||
|
||||
assert res.status_code == 200
|
||||
soup = BeautifulSoup(res.content.decode(), "lxml")
|
||||
soup = BeautifulSoup(res.text, "lxml")
|
||||
detail_html = soup.find(id="club_detail")
|
||||
assert detail_html.find_all("markdown") == []
|
||||
|
@ -37,6 +37,7 @@ from django.http import (
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext as _t
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DetailView, ListView, View
|
||||
@ -250,6 +251,10 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
|
||||
template_name = "club/club_members.jinja"
|
||||
current_tab = "members"
|
||||
|
||||
@cached_property
|
||||
def members(self) -> list[Membership]:
|
||||
return list(self.object.members.ongoing().order_by("-role"))
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["request_user"] = self.request.user
|
||||
@ -257,8 +262,8 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
|
||||
kwargs["club_members"] = self.members
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
kwargs = super().get_context_data(*args, **kwargs)
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
kwargs["members"] = self.members
|
||||
return kwargs
|
||||
|
||||
@ -277,12 +282,8 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
|
||||
membership.save()
|
||||
return resp
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.members = self.get_object().members.ongoing().order_by("-role")
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
return reverse_lazy("club:club_members", kwargs={"club_id": self.object.id})
|
||||
return self.request.path
|
||||
|
||||
|
||||
class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
|
||||
|
@ -1,7 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
@ -166,9 +165,9 @@ class TestFetchNewsDates(TestCase):
|
||||
assert dates[1]["news"]["summary"] == markdown(summary_2)
|
||||
|
||||
def test_fetch(self):
|
||||
after = quote((now() + timedelta(days=1)).isoformat())
|
||||
after = (now() + timedelta(days=1)).isoformat()
|
||||
response = self.client.get(
|
||||
reverse("api:fetch_news_dates") + f"?page_size=3&after={after}"
|
||||
reverse("api:fetch_news_dates", query={"page_size": 3, "after": after})
|
||||
)
|
||||
assert response.status_code == 200
|
||||
dates = response.json()["results"]
|
||||
|
@ -227,6 +227,19 @@ class FormerSubscriberMixin(AccessMixin):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class IsSubscriberMixin(AccessMixin):
|
||||
"""Check if the user is a subscriber.
|
||||
|
||||
Raises:
|
||||
PermissionDenied: if the user isn't subscribed.
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_subscribed:
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class PermissionOrAuthorRequiredMixin(PermissionRequiredMixin):
|
||||
"""Require that the user has the required perm or is the object author.
|
||||
|
||||
|
@ -839,8 +839,7 @@ Welcome to the wiki page!
|
||||
accounting_admin.permissions.add(
|
||||
*list(
|
||||
perms.filter(
|
||||
Q(content_type__app_label="accounting")
|
||||
| Q(
|
||||
Q(
|
||||
codename__in=[
|
||||
"view_customer",
|
||||
"view_product",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 27 KiB |
@ -361,19 +361,22 @@ body {
|
||||
align-items: center;
|
||||
text-align: justify;
|
||||
|
||||
&.alert-yellow {
|
||||
&.alert-yellow,
|
||||
&.alert-warning {
|
||||
background-color: rgb(255, 255, 240);
|
||||
color: rgb(99, 87, 6);
|
||||
border: rgb(192, 180, 16) 1px solid;
|
||||
}
|
||||
|
||||
&.alert-green {
|
||||
&.alert-green,
|
||||
&.alert-success {
|
||||
background-color: rgb(245, 255, 245);
|
||||
color: rgb(3, 84, 63);
|
||||
border: rgb(14, 159, 110) 1px solid;
|
||||
}
|
||||
|
||||
&.alert-red {
|
||||
&.alert-red,
|
||||
&.alert-error {
|
||||
background-color: rgb(255, 245, 245);
|
||||
color: #c53030;
|
||||
border: #fc8181 1px solid;
|
||||
@ -523,33 +526,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------ACCOUNTING----------------------------*/
|
||||
#accounting {
|
||||
.journal-table {
|
||||
tbody {
|
||||
.neg-amount {
|
||||
color: red;
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
font-size: 1em;
|
||||
content: "\f063";
|
||||
}
|
||||
}
|
||||
|
||||
.pos-amount {
|
||||
color: green;
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
font-size: 1em;
|
||||
content: "\f062";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------GENERAL-----------------------------*/
|
||||
h1,
|
||||
h2,
|
||||
|
@ -100,8 +100,9 @@ class TestUserRegistration:
|
||||
payload = valid_payload | payload_edit
|
||||
response = client.post(reverse("core:register"), payload)
|
||||
assert response.status_code == 200
|
||||
error_html = f'<ul class="errorlist"><li>{expected_error}</li></ul>'
|
||||
assertInHTML(error_html, str(response.content.decode()))
|
||||
errors = BeautifulSoup(response.text, "lxml").find_all(class_="errorlist")
|
||||
assert len(errors) == 1
|
||||
assert errors[0].text == expected_error
|
||||
assert not User.objects.filter(email=payload["email"]).exists()
|
||||
|
||||
def test_register_honeypot_fail(self, client: Client, valid_payload):
|
||||
@ -124,7 +125,7 @@ class TestUserRegistration:
|
||||
error_html = (
|
||||
"<li>Un objet Utilisateur avec ce champ Adresse email existe déjà.</li>"
|
||||
)
|
||||
assertInHTML(error_html, str(response.content.decode()))
|
||||
assertInHTML(error_html, str(response.text))
|
||||
|
||||
def test_register_fail_with_not_existing_email(
|
||||
self, client: Client, valid_payload, monkeypatch
|
||||
@ -141,7 +142,7 @@ class TestUserRegistration:
|
||||
error_html = (
|
||||
"<li>Nous n'avons pas réussi à vérifier que cette adresse mail existe.</li>"
|
||||
)
|
||||
assertInHTML(error_html, str(response.content.decode()))
|
||||
assertInHTML(error_html, str(response.text))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@ -160,7 +161,7 @@ class TestUserLogin:
|
||||
assert (
|
||||
'<p class="alert alert-red">Votre nom d\'utilisateur '
|
||||
"et votre mot de passe ne correspondent pas. Merci de réessayer.</p>"
|
||||
) in str(response.content.decode())
|
||||
) in response.text
|
||||
assert response.wsgi_request.user.is_anonymous
|
||||
|
||||
def test_login_success(self, client, user):
|
||||
@ -246,7 +247,7 @@ class TestPageHandling(TestCase):
|
||||
|
||||
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
|
||||
assert response.status_code == 200
|
||||
html = response.content.decode()
|
||||
html = response.text
|
||||
assert '<a href="/page/guy/hist/">' in html
|
||||
assert '<a href="/page/guy/edit/">' in html
|
||||
assert '<a href="/page/guy/prop/">' in html
|
||||
@ -256,12 +257,12 @@ class TestPageHandling(TestCase):
|
||||
parent = baker.prepare(Page)
|
||||
parent.save(force_lock=True)
|
||||
response = self.client.get(
|
||||
reverse("core:page_new") + f"?page={parent._full_name}/new"
|
||||
reverse("core:page_new", query={"page": f"{parent._full_name}/new"})
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
# The name and parent inputs should be already filled
|
||||
soup = BeautifulSoup(response.content.decode(), "lxml")
|
||||
soup = BeautifulSoup(response.text, "lxml")
|
||||
assert soup.find("input", {"name": "name"})["value"] == "new"
|
||||
select = soup.find("autocomplete-select", {"name": "parent"})
|
||||
assert select.find("option", {"selected": True})["value"] == str(parent.id)
|
||||
@ -278,7 +279,7 @@ class TestPageHandling(TestCase):
|
||||
assertRedirects(response, new_url, fetch_redirect_response=False)
|
||||
response = self.client.get(new_url)
|
||||
assert response.status_code == 200
|
||||
assert f'<a href="/page/{parent._full_name}/new/">' in response.content.decode()
|
||||
assert f'<a href="/page/{parent._full_name}/new/">' in response.text
|
||||
|
||||
def test_access_child_page_ok(self):
|
||||
"""Should display a page correctly."""
|
||||
@ -290,14 +291,14 @@ class TestPageHandling(TestCase):
|
||||
reverse("core:page", kwargs={"page_name": "guy/bibou"})
|
||||
)
|
||||
assert response.status_code == 200
|
||||
html = response.content.decode()
|
||||
html = response.text
|
||||
self.assertIn('<a href="/page/guy/bibou/edit/">', html)
|
||||
|
||||
def test_access_page_not_found(self):
|
||||
"""Should not display a page correctly."""
|
||||
response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
|
||||
assert response.status_code == 200
|
||||
html = response.content.decode()
|
||||
html = response.text
|
||||
self.assertIn('<a href="/page/create/?page=swagg">', html)
|
||||
|
||||
def test_create_page_markdown_safe(self):
|
||||
@ -331,7 +332,7 @@ http://git.an
|
||||
<p><guy>Bibou</guy></p>
|
||||
<p><script>alert('Guy');</script></p>
|
||||
"""
|
||||
assertInHTML(expected, response.content.decode())
|
||||
assertInHTML(expected, response.text)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@ -340,7 +341,9 @@ class TestUserTools:
|
||||
"""An anonymous user shouldn't have access to the tools page."""
|
||||
url = reverse("core:user_tools")
|
||||
response = client.get(url)
|
||||
assertRedirects(response, expected_url=reverse("core:login") + f"?next={url}")
|
||||
assertRedirects(
|
||||
response, expected_url=reverse("core:login", query={"next": url})
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("username", ["guy", "root", "skia", "comunity"])
|
||||
def test_page_is_working(self, client, username):
|
||||
|
@ -147,8 +147,8 @@ class TestUserProfilePicture:
|
||||
reverse(
|
||||
"core:file_delete",
|
||||
kwargs={"file_id": user.profile_pict.pk, "popup": ""},
|
||||
)
|
||||
+ f"?next={user.get_absolute_url()}"
|
||||
query={"next": user.get_absolute_url()},
|
||||
),
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -72,7 +72,9 @@ class TestSearchUsersAPI(TestSearchUsers):
|
||||
|
||||
expected = [u.id for u in self.users[::-1]]
|
||||
for term in ["first", "First", "FIRST"]:
|
||||
response = self.client.get(reverse("api:search_users") + f"?search={term}")
|
||||
response = self.client.get(
|
||||
reverse("api:search_users", query={"search": term})
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["count"] == 11
|
||||
assert [r["id"] for r in response.json()["results"]] == expected
|
||||
|
@ -5,12 +5,12 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="accounting">
|
||||
<main>
|
||||
<h3>{% trans %}Refound account{% endtrans %}</h3>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p() }}
|
||||
<p><input type="submit" value="{% trans %}Refound{% endtrans %}" /></p>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
@ -633,7 +633,7 @@ class TestCounterStats(TestCase):
|
||||
def test_not_authenticated_access_fail(self):
|
||||
url = reverse("counter:stats", args=[self.counter.id])
|
||||
response = self.client.get(url)
|
||||
assertRedirects(response, reverse("core:login") + f"?next={url}")
|
||||
assertRedirects(response, reverse("core:login", query={"next": url}))
|
||||
|
||||
def test_unauthorized_user_fails(self):
|
||||
self.client.force_login(baker.make(User))
|
||||
|
@ -1,5 +1,4 @@
|
||||
import itertools
|
||||
import json
|
||||
import string
|
||||
from datetime import timedelta
|
||||
|
||||
@ -16,7 +15,6 @@ from core.baker_recipes import board_user, subscriber_user
|
||||
from core.models import User
|
||||
from counter.baker_recipes import product_recipe, refill_recipe, sale_recipe
|
||||
from counter.models import (
|
||||
BillingInfo,
|
||||
Counter,
|
||||
Customer,
|
||||
Refilling,
|
||||
@ -26,149 +24,6 @@ from counter.models import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestBillingInfo:
|
||||
@pytest.fixture
|
||||
def payload(self):
|
||||
return {
|
||||
"first_name": "Subscribed",
|
||||
"last_name": "User",
|
||||
"address_1": "3, rue de Troyes",
|
||||
"zip_code": "34301",
|
||||
"city": "Sète",
|
||||
"country": "FR",
|
||||
"phone_number": "0612345678",
|
||||
}
|
||||
|
||||
def test_edit_infos(self, client: Client, payload: dict):
|
||||
user = subscriber_user.make()
|
||||
baker.make(BillingInfo, customer=user.customer)
|
||||
client.force_login(user)
|
||||
response = client.put(
|
||||
reverse("api:put_billing_info", args=[user.id]),
|
||||
json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
user.refresh_from_db()
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert response.status_code == 200
|
||||
assert hasattr(user.customer, "billing_infos")
|
||||
assert infos.customer == user.customer
|
||||
for key, val in payload.items():
|
||||
assert getattr(infos, key) == val
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"user_maker", [subscriber_user.make, lambda: baker.make(User)]
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_create_infos(self, client: Client, user_maker, payload):
|
||||
user = user_maker()
|
||||
client.force_login(user)
|
||||
assert not BillingInfo.objects.filter(customer__user=user).exists()
|
||||
response = client.put(
|
||||
reverse("api:put_billing_info", args=[user.id]),
|
||||
json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
user.refresh_from_db()
|
||||
assert hasattr(user, "customer")
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert hasattr(user.customer, "billing_infos")
|
||||
assert infos.customer == user.customer
|
||||
for key, val in payload.items():
|
||||
assert getattr(infos, key) == val
|
||||
|
||||
def test_invalid_data(self, client: Client, payload: dict[str, str]):
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
# address_1, zip_code and country are missing
|
||||
del payload["city"]
|
||||
response = client.put(
|
||||
reverse("api:put_billing_info", args=[user.id]),
|
||||
json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 422
|
||||
user.customer.refresh_from_db()
|
||||
assert not hasattr(user.customer, "billing_infos")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("operator_maker", "expected_code"),
|
||||
[
|
||||
(subscriber_user.make, 403),
|
||||
(lambda: baker.make(User), 403),
|
||||
(lambda: baker.make(User, is_superuser=True), 200),
|
||||
],
|
||||
)
|
||||
def test_edit_other_user(
|
||||
self, client: Client, operator_maker, expected_code: int, payload: dict
|
||||
):
|
||||
user = subscriber_user.make()
|
||||
client.force_login(operator_maker())
|
||||
baker.make(BillingInfo, customer=user.customer)
|
||||
response = client.put(
|
||||
reverse("api:put_billing_info", args=[user.id]),
|
||||
json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == expected_code
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"phone_number",
|
||||
["+33612345678", "0612345678", "06 12 34 56 78", "06-12-34-56-78"],
|
||||
)
|
||||
def test_phone_number_format(
|
||||
self, client: Client, payload: dict, phone_number: str
|
||||
):
|
||||
"""Test that various formats of phone numbers are accepted."""
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
payload["phone_number"] = phone_number
|
||||
response = client.put(
|
||||
reverse("api:put_billing_info", args=[user.id]),
|
||||
json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert infos.phone_number == "0612345678"
|
||||
assert infos.phone_number.country_code == 33
|
||||
|
||||
def test_foreign_phone_number(self, client: Client, payload: dict):
|
||||
"""Test that a foreign phone number is accepted."""
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
payload["phone_number"] = "+49612345678"
|
||||
response = client.put(
|
||||
reverse("api:put_billing_info", args=[user.id]),
|
||||
json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert infos.phone_number.as_national == "06123 45678"
|
||||
assert infos.phone_number.country_code == 49
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"phone_number", ["061234567a", "06 12 34 56", "061234567879", "azertyuiop"]
|
||||
)
|
||||
def test_invalid_phone_number(
|
||||
self, client: Client, payload: dict, phone_number: str
|
||||
):
|
||||
"""Test that invalid phone numbers are rejected."""
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
payload["phone_number"] = phone_number
|
||||
response = client.put(
|
||||
reverse("api:put_billing_info", args=[user.id]),
|
||||
json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert not BillingInfo.objects.filter(customer__user=user).exists()
|
||||
|
||||
|
||||
class TestStudentCard(TestCase):
|
||||
"""Tests for adding and deleting Stundent Cards
|
||||
Test that an user can be found with it's student card.
|
||||
|
@ -24,66 +24,64 @@ sith/
|
||||
├── .github/
|
||||
│ ├── actions/ (1)
|
||||
│ └── workflows/ (2)
|
||||
├── accounting/ (3)
|
||||
├── club/ (3)
|
||||
│ └── ...
|
||||
├── club/ (4)
|
||||
├── com/ (4)
|
||||
│ └── ...
|
||||
├── com/ (5)
|
||||
├── core/ (5)
|
||||
│ └── ...
|
||||
├── core/ (6)
|
||||
├── counter/ (6)
|
||||
│ └── ...
|
||||
├── counter/ (7)
|
||||
├── docs/ (7)
|
||||
│ └── ...
|
||||
├── docs/ (8)
|
||||
├── eboutic/ (8)
|
||||
│ └── ...
|
||||
├── eboutic/ (9)
|
||||
├── election/ (9)
|
||||
│ └── ...
|
||||
├── election/ (10)
|
||||
├── forum/ (10)
|
||||
│ └── ...
|
||||
├── forum/ (11)
|
||||
├── galaxy/ (11)
|
||||
│ └── ...
|
||||
├── galaxy/ (12)
|
||||
├── launderette/ (12)
|
||||
│ └── ...
|
||||
├── launderette/ (13)
|
||||
├── locale/ (13)
|
||||
│ └── ...
|
||||
├── locale/ (14)
|
||||
├── matmat/ (14)
|
||||
│ └── ...
|
||||
├── matmat/ (15)
|
||||
├── pedagogy/ (15)
|
||||
│ └── ...
|
||||
├── pedagogy/ (16)
|
||||
├── rootplace/ (16)
|
||||
│ └── ...
|
||||
├── rootplace/ (17)
|
||||
├── sas/ (17)
|
||||
│ └── ...
|
||||
├── sas/ (18)
|
||||
├── sith/ (18)
|
||||
│ └── ...
|
||||
├── sith/ (19)
|
||||
├── subscription/ (19)
|
||||
│ └── ...
|
||||
├── subscription/ (20)
|
||||
├── trombi/ (20)
|
||||
│ └── ...
|
||||
├── trombi/ (21)
|
||||
├── antispam/ (21)
|
||||
│ └── ...
|
||||
├── antispam/ (22)
|
||||
├── staticfiles/ (22)
|
||||
│ └── ...
|
||||
├── staticfiles/ (23)
|
||||
│ └── ...
|
||||
├── processes/ (24)
|
||||
├── processes/ (23)
|
||||
│ └── ...
|
||||
│
|
||||
├── .coveragerc (25)
|
||||
├── .envrc (26)
|
||||
├── .coveragerc (24)
|
||||
├── .envrc (25)
|
||||
├── .gitattributes
|
||||
├── .gitignore
|
||||
├── .mailmap
|
||||
├── .env (27)
|
||||
├── .env.example (28)
|
||||
├── manage.py (29)
|
||||
├── mkdocs.yml (30)
|
||||
├── .env (26)
|
||||
├── .env.example (27)
|
||||
├── manage.py (28)
|
||||
├── mkdocs.yml (29)
|
||||
├── uv.lock
|
||||
├── pyproject.toml (31)
|
||||
├── .venv/ (32)
|
||||
├── .python-version (33)
|
||||
├── Procfile.static (34)
|
||||
├── Procfile.service (35)
|
||||
├── pyproject.toml (30)
|
||||
├── .venv/ (31)
|
||||
├── .python-version (32)
|
||||
├── Procfile.static (33)
|
||||
├── Procfile.service (34)
|
||||
└── README.md
|
||||
```
|
||||
</div>
|
||||
@ -96,55 +94,54 @@ sith/
|
||||
des workflows Github.
|
||||
Par exemple, le workflow `docs.yml` compile
|
||||
et publie la documentation à chaque push sur la branche `master`.
|
||||
3. Application de gestion de la comptabilité.
|
||||
4. Application de gestion des clubs et de leurs membres.
|
||||
5. Application contenant les fonctionnalités
|
||||
3. Application de gestion des clubs et de leurs membres.
|
||||
4. Application contenant les fonctionnalités
|
||||
destinées aux responsables communication de l'AE.
|
||||
6. Application contenant la modélisation centrale du site.
|
||||
5. Application contenant la modélisation centrale du site.
|
||||
On en reparle plus loin sur cette page.
|
||||
7. Application de gestion des comptoirs, des permanences
|
||||
6. Application de gestion des comptoirs, des permanences
|
||||
sur ces comptoirs et des transactions qui y sont effectuées.
|
||||
8. Dossier contenant la documentation.
|
||||
9. Application de gestion de la boutique en ligne.
|
||||
10. Application de gestion des élections.
|
||||
11. Application de gestion du forum
|
||||
12. Application de gestion de la galaxie ; la galaxie
|
||||
7. Dossier contenant la documentation.
|
||||
8. Application de gestion de la boutique en ligne.
|
||||
9. Application de gestion des élections.
|
||||
10. Application de gestion du forum
|
||||
11. Application de gestion de la galaxie ; la galaxie
|
||||
est un graphe des niveaux de proximité entre les différents
|
||||
étudiants.
|
||||
13. Gestion des machines à laver de l'AE
|
||||
14. Dossier contenant les fichiers de traduction.
|
||||
15. Fonctionnalités de recherche d'utilisateurs.
|
||||
16. Le guide des UEs du site, sur lequel les utilisateurs
|
||||
12. Gestion des machines à laver de l'AE
|
||||
13. Dossier contenant les fichiers de traduction.
|
||||
14. Fonctionnalités de recherche d'utilisateurs.
|
||||
15. Le guide des UEs du site, sur lequel les utilisateurs
|
||||
peuvent également laisser leurs avis.
|
||||
17. Fonctionnalités utiles aux utilisateurs root.
|
||||
18. Le SAS, où l'on trouve toutes les photos de l'AE.
|
||||
19. Application principale du projet, contenant sa configuration.
|
||||
20. Gestion des cotisations des utilisateurs du site.
|
||||
21. Outil pour faciliter la fabrication des trombinoscopes de promo.
|
||||
22. Fonctionnalités pour gérer le spam.
|
||||
23. Gestion des statics du site. Override le système de statics de Django.
|
||||
16. Fonctionnalités utiles aux utilisateurs root.
|
||||
17. Le SAS, où l'on trouve toutes les photos de l'AE.
|
||||
18. Application principale du projet, contenant sa configuration.
|
||||
19. Gestion des cotisations des utilisateurs du site.
|
||||
20. Outil pour faciliter la fabrication des trombinoscopes de promo.
|
||||
21. Fonctionnalités pour gérer le spam.
|
||||
22. Gestion des statics du site. Override le système de statics de Django.
|
||||
Ajoute l'intégration du scss et du bundler js
|
||||
de manière transparente pour l'utilisateur.
|
||||
24. Module de gestion des services externes.
|
||||
23. Module de gestion des services externes.
|
||||
Offre une API simple pour utiliser les fichiers `Procfile.*`.
|
||||
25. Fichier de configuration de coverage.
|
||||
26. Fichier de configuration de direnv.
|
||||
27. Contient les variables d'environnement, qui sont susceptibles
|
||||
24. Fichier de configuration de coverage.
|
||||
25. Fichier de configuration de direnv.
|
||||
26. Contient les variables d'environnement, qui sont susceptibles
|
||||
de varier d'une machine à l'autre.
|
||||
28. Contient des valeurs par défaut pour le `.env`
|
||||
27. Contient des valeurs par défaut pour le `.env`
|
||||
pouvant convenir à un environnment de développement local
|
||||
29. Fichier généré automatiquement par Django. C'est lui
|
||||
28. Fichier généré automatiquement par Django. C'est lui
|
||||
qui permet d'appeler des commandes de gestion du projet
|
||||
avec la syntaxe `python ./manage.py <nom de la commande>`
|
||||
30. Le fichier de configuration de la documentation,
|
||||
29. Le fichier de configuration de la documentation,
|
||||
avec ses plugins et sa table des matières.
|
||||
31. Le fichier où sont déclarés les dépendances et la configuration
|
||||
30. Le fichier où sont déclarés les dépendances et la configuration
|
||||
de certaines d'entre elles.
|
||||
32. Dossier d'environnement virtuel généré par uv
|
||||
33. Fichier qui contrôle quelle version de python utiliser pour le projet
|
||||
34. Fichier qui contrôle les commandes à lancer pour gérer la compilation
|
||||
31. Dossier d'environnement virtuel généré par uv
|
||||
32. Fichier qui contrôle quelle version de python utiliser pour le projet
|
||||
33. Fichier qui contrôle les commandes à lancer pour gérer la compilation
|
||||
automatique des static et autres services nécessaires à la command runserver.
|
||||
35. Fichier qui contrôle les services tiers nécessaires au fonctionnement
|
||||
34. Fichier qui contrôle les services tiers nécessaires au fonctionnement
|
||||
du Sith tel que redis.
|
||||
|
||||
## L'application principale
|
||||
|
@ -1,31 +1,13 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja_extra import ControllerBase, api_controller, route
|
||||
from ninja_extra.exceptions import NotFound, PermissionDenied
|
||||
from ninja_extra.exceptions import NotFound
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from pydantic import NonNegativeInt
|
||||
|
||||
from core.models import User
|
||||
from counter.models import BillingInfo, Customer
|
||||
from counter.models import BillingInfo
|
||||
from eboutic.models import Basket
|
||||
from eboutic.schemas import BillingInfoSchema
|
||||
|
||||
|
||||
@api_controller("/etransaction", permissions=[IsAuthenticated])
|
||||
class EtransactionInfoController(ControllerBase):
|
||||
@route.put("/billing-info/{user_id}", url_name="put_billing_info")
|
||||
def put_user_billing_info(self, user_id: NonNegativeInt, info: BillingInfoSchema):
|
||||
"""Update or create the billing info of this user."""
|
||||
if user_id == self.context.request.user.id:
|
||||
user = self.context.request.user
|
||||
elif self.context.request.user.is_root:
|
||||
user = get_object_or_404(User, pk=user_id)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
customer, _ = Customer.get_or_create(user)
|
||||
BillingInfo.objects.update_or_create(
|
||||
customer=customer, defaults=info.model_dump(exclude_none=True)
|
||||
)
|
||||
|
||||
@route.get("/data", url_name="etransaction_data")
|
||||
def fetch_etransaction_data(self):
|
||||
"""Generate the data to pay an eboutic command with paybox.
|
||||
@ -35,4 +17,7 @@ class EtransactionInfoController(ControllerBase):
|
||||
basket = Basket.from_session(self.context.request.session)
|
||||
if basket is None:
|
||||
raise NotFound
|
||||
try:
|
||||
return dict(basket.get_e_transaction_data())
|
||||
except BillingInfo.DoesNotExist as e:
|
||||
raise NotFound from e
|
||||
|
@ -16,6 +16,7 @@ from __future__ import annotations
|
||||
|
||||
import hmac
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Self
|
||||
|
||||
from dict2xml import dict2xml
|
||||
@ -44,6 +45,30 @@ def get_eboutic_products(user: User) -> list[Product]:
|
||||
return [p for p in products if p.can_be_sold_to(user)]
|
||||
|
||||
|
||||
class BillingInfoState(Enum):
|
||||
VALID = 1
|
||||
EMPTY = 2
|
||||
MISSING_PHONE_NUMBER = 3
|
||||
|
||||
@classmethod
|
||||
def from_model(cls, info: BillingInfo | None) -> BillingInfoState:
|
||||
if info is None:
|
||||
return cls.EMPTY
|
||||
for attr in [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"address_1",
|
||||
"zip_code",
|
||||
"city",
|
||||
"country",
|
||||
]:
|
||||
if getattr(info, attr) == "":
|
||||
return cls.EMPTY
|
||||
if info.phone_number is None:
|
||||
return cls.MISSING_PHONE_NUMBER
|
||||
return cls.VALID
|
||||
|
||||
|
||||
class Basket(models.Model):
|
||||
"""Basket is built when the user connects to an eboutic page."""
|
||||
|
||||
@ -127,7 +152,11 @@ class Basket(models.Model):
|
||||
if not hasattr(user, "customer"):
|
||||
raise Customer.DoesNotExist
|
||||
customer = user.customer
|
||||
if not hasattr(user.customer, "billing_infos"):
|
||||
if (
|
||||
not hasattr(user.customer, "billing_infos")
|
||||
or BillingInfoState.from_model(user.customer.billing_infos)
|
||||
!= BillingInfoState.VALID
|
||||
):
|
||||
raise BillingInfo.DoesNotExist
|
||||
cart = {
|
||||
"shoppingcart": {"total": {"totalQuantity": min(self.items.count(), 99)}}
|
||||
|
@ -1,91 +1,17 @@
|
||||
import { exportToHtml } from "#core:utils/globals";
|
||||
import {
|
||||
type BillingInfoSchema,
|
||||
etransactioninfoFetchEtransactionData,
|
||||
etransactioninfoPutUserBillingInfo,
|
||||
} from "#openapi";
|
||||
|
||||
enum BillingInfoReqState {
|
||||
Success = "0",
|
||||
Failure = "1",
|
||||
Sending = "2",
|
||||
}
|
||||
|
||||
exportToHtml("BillingInfoReqState", BillingInfoReqState);
|
||||
import { etransactioninfoFetchEtransactionData } from "#openapi";
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("etransactionData", (initialData) => ({
|
||||
Alpine.data("etransaction", (initialData) => ({
|
||||
data: initialData,
|
||||
isCbAvailable: Object.keys(initialData).length > 0,
|
||||
|
||||
async fill() {
|
||||
const button = document.getElementById("bank-submit-button") as HTMLButtonElement;
|
||||
button.disabled = true;
|
||||
this.isCbAvailable = false;
|
||||
const res = await etransactioninfoFetchEtransactionData();
|
||||
if (res.response.ok) {
|
||||
this.data = res.data;
|
||||
button.disabled = false;
|
||||
this.isCbAvailable = true;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
Alpine.data("billing_infos", (userId: number) => ({
|
||||
/** @type {BillingInfoReqState | null} */
|
||||
reqState: null,
|
||||
|
||||
async sendForm() {
|
||||
this.reqState = BillingInfoReqState.Sending;
|
||||
const form = document.getElementById("billing_info_form");
|
||||
const submitButton = document.getElementById(
|
||||
"bank-submit-button",
|
||||
) as HTMLButtonElement;
|
||||
submitButton.disabled = true;
|
||||
const payload = Object.fromEntries(
|
||||
Array.from(form.querySelectorAll("input, select"))
|
||||
.filter((elem: HTMLInputElement) => elem.type !== "submit" && elem.value)
|
||||
.map((elem: HTMLInputElement) => [elem.name, elem.value]),
|
||||
);
|
||||
const res = await etransactioninfoPutUserBillingInfo({
|
||||
// biome-ignore lint/style/useNamingConvention: API is snake_case
|
||||
path: { user_id: userId },
|
||||
body: payload as unknown as BillingInfoSchema,
|
||||
});
|
||||
this.reqState = res.response.ok
|
||||
? BillingInfoReqState.Success
|
||||
: BillingInfoReqState.Failure;
|
||||
if (res.response.status === 422) {
|
||||
const errors = await res.response
|
||||
.json()
|
||||
.detail.flatMap((err: Record<"loc", string>) => err.loc);
|
||||
for (const elem of Array.from(form.querySelectorAll("input")).filter((elem) =>
|
||||
errors.includes(elem.name),
|
||||
)) {
|
||||
elem.setCustomValidity(gettext("Incorrect value"));
|
||||
elem.reportValidity();
|
||||
elem.oninput = () => elem.setCustomValidity("");
|
||||
}
|
||||
} else if (res.response.ok) {
|
||||
this.$dispatch("billing-infos-filled");
|
||||
}
|
||||
},
|
||||
|
||||
getAlertColor() {
|
||||
if (this.reqState === BillingInfoReqState.Success) {
|
||||
return "green";
|
||||
}
|
||||
if (this.reqState === BillingInfoReqState.Failure) {
|
||||
return "red";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
getAlertMessage() {
|
||||
if (this.reqState === BillingInfoReqState.Success) {
|
||||
return gettext("Billing info registration success");
|
||||
}
|
||||
if (this.reqState === BillingInfoReqState.Failure) {
|
||||
return gettext("Billing info registration failure");
|
||||
}
|
||||
return "";
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
42
eboutic/templates/eboutic/eboutic_billing_info.jinja
Normal file
42
eboutic/templates/eboutic/eboutic_billing_info.jinja
Normal file
@ -0,0 +1,42 @@
|
||||
<div id=billing-infos-fragment>
|
||||
<div
|
||||
class="collapse"
|
||||
:class="{'shadow': collapsed}"
|
||||
x-data="{collapsed: {{ "true" if messages or form.errors else "false" }}}"
|
||||
>
|
||||
<div class="collapse-header clickable" @click="collapsed = !collapsed">
|
||||
<span class="collapse-header-text">
|
||||
{% trans %}Billing information{% endtrans %}
|
||||
</span>
|
||||
<span class="collapse-header-icon" :class="{'reverse': collapsed}">
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
class="collapse-body"
|
||||
hx-trigger="submit"
|
||||
hx-post="{{ action }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#billing-infos-fragment"
|
||||
x-show="collapsed"
|
||||
>
|
||||
{% csrf_token %}
|
||||
{{ form.as_p() }}
|
||||
<br>
|
||||
<input
|
||||
type="submit" class="btn btn-blue clickable"
|
||||
value="{% trans %}Validate{% endtrans %}"
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
@ -66,7 +66,6 @@
|
||||
{% trans %}Clear{% endtrans %}
|
||||
</button>
|
||||
<form method="get" action="{{ url('eboutic:command') }}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-blue">
|
||||
<i class="fa fa-check"></i>
|
||||
<input type="submit" value="{% trans %}Validate{% endtrans %}"/>
|
||||
@ -96,23 +95,36 @@
|
||||
<div class="category-header">
|
||||
<h3 class="margin-bottom">{% trans %}Eurockéennes 2025 partnership{% endtrans %}</h3>
|
||||
{% if user.is_subscribed %}
|
||||
<a
|
||||
title="Logiciel billetterie en ligne"
|
||||
href="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||
class="weezevent-widget-integration"
|
||||
target="_blank"
|
||||
data-src="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||
data-width="650"
|
||||
data-height="600"
|
||||
data-resize="1"
|
||||
data-nopb="0"
|
||||
data-type="neo"
|
||||
data-width_auto="1"
|
||||
data-noscroll="0"
|
||||
data-id="1211855">
|
||||
Billetterie Weezevent
|
||||
</a>
|
||||
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>
|
||||
<div id="eurok-partner" style="
|
||||
min-height: 600px;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
">
|
||||
<p style="text-align: center;">
|
||||
{% trans trimmed %}
|
||||
Our partner uses Weezevent to sell tickets.
|
||||
Weezevent may collect user info according to
|
||||
it's own privacy policy.
|
||||
By clicking the accept button you consent to
|
||||
their terms of services.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
|
||||
<a href="https://weezevent.com/fr/politique-de-confidentialite/">{% trans %}Privacy policy{% endtrans %}</a>
|
||||
|
||||
<button
|
||||
hx-get="{{ url("eboutic:eurok") }}"
|
||||
hx-target="#eurok-partner"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="click, load[document.cookie.includes('weezevent_accept=true')]"
|
||||
@htmx:after-request="document.cookie = 'weezevent_accept=true'"
|
||||
>{% trans %}Accept{% endtrans %}
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
{%- trans trimmed %}
|
||||
|
@ -15,7 +15,11 @@
|
||||
{% block content %}
|
||||
<h3>{% trans %}Eboutic{% endtrans %}</h3>
|
||||
|
||||
<div>
|
||||
<script type="text/javascript">
|
||||
let billingInfos = {{ billing_infos|safe }};
|
||||
</script>
|
||||
|
||||
<div x-data="etransaction(billingInfos)">
|
||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||
<table>
|
||||
<thead>
|
||||
@ -53,80 +57,22 @@
|
||||
</p>
|
||||
<br>
|
||||
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
|
||||
<div
|
||||
class="collapse"
|
||||
:class="{'shadow': collapsed}"
|
||||
x-data="{collapsed: !{{ "true" if billing_infos else "false" }}}"
|
||||
x-cloak
|
||||
>
|
||||
<div class="collapse-header clickable" @click="collapsed = !collapsed">
|
||||
<span class="collapse-header-text">
|
||||
{% trans %}Billing information{% endtrans %}
|
||||
</span>
|
||||
<span class="collapse-header-icon" :class="{'reverse': collapsed}">
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</span>
|
||||
<div @htmx:after-request="fill">
|
||||
{{ billing_infos_form }}
|
||||
</div>
|
||||
<form
|
||||
class="collapse-body"
|
||||
id="billing_info_form"
|
||||
x-data="billing_infos({{ user.id }})"
|
||||
x-show="collapsed"
|
||||
x-transition.scale.origin.top
|
||||
@submit.prevent="await sendForm()"
|
||||
>
|
||||
{% csrf_token %}
|
||||
{{ billing_form }}
|
||||
<br />
|
||||
<div
|
||||
x-show="[BillingInfoReqState.Success, BillingInfoReqState.Failure].includes(reqState)"
|
||||
class="alert"
|
||||
:class="'alert-' + getAlertColor()"
|
||||
x-transition
|
||||
>
|
||||
<div class="alert-main" x-text="getAlertMessage()"></div>
|
||||
<div class="clickable" @click="reqState = null">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="submit" class="btn btn-blue clickable"
|
||||
value="{% trans %}Validate{% endtrans %}"
|
||||
:disabled="reqState === BillingInfoReqState.Sending"
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
{% if billing_infos_state == BillingInfoState.EMPTY %}
|
||||
<div class="alert alert-yellow">
|
||||
{% trans trimmed %}
|
||||
You must fill your billing infos if you want to pay with your credit card
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %}
|
||||
<div class="alert alert-yellow">
|
||||
{% trans trimmed %}
|
||||
The Crédit Agricole changed its policy related to the billing
|
||||
information that must be provided in order to pay with a credit card.
|
||||
If you want to pay with your credit card, you must add a phone number
|
||||
to the data you already provided.
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form
|
||||
method="post"
|
||||
action="{{ settings.SITH_EBOUTIC_ET_URL }}"
|
||||
name="bank-pay-form"
|
||||
x-data="etransactionData(initialEtData)"
|
||||
@billing-infos-filled.window="await fill()"
|
||||
>
|
||||
<template x-for="[key, value] in Object.entries(data)" :key="key">
|
||||
<input type="hidden" :name="key" :value="value">
|
||||
</template>
|
||||
<input
|
||||
x-cloak
|
||||
type="submit"
|
||||
id="bank-submit-button"
|
||||
{% if billing_infos_state != BillingInfoState.VALID %}disabled="disabled"{% endif %}
|
||||
:disabled="!isCbAvailable"
|
||||
class="btn btn-blue"
|
||||
value="{% trans %}Pay with credit card{% endtrans %}"
|
||||
/>
|
||||
</form>
|
||||
@ -139,20 +85,8 @@
|
||||
<form method="post" action="{{ url('eboutic:pay_with_sith') }}" name="sith-pay-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="pay_with_sith_account">
|
||||
<input type="submit" value="{% trans %}Pay with Sith account{% endtrans %}"/>
|
||||
<input class="btn btn-blue" type="submit" value="{% trans %}Pay with Sith account{% endtrans %}"/>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
{% if billing_infos -%}
|
||||
const initialEtData = {{ billing_infos|safe }}
|
||||
{%- else -%}
|
||||
const initialEtData = {}
|
||||
{%- endif %}
|
||||
</script>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
|
17
eboutic/templates/eboutic/eurok_fragment.jinja
Normal file
17
eboutic/templates/eboutic/eurok_fragment.jinja
Normal file
@ -0,0 +1,17 @@
|
||||
<a
|
||||
title="Logiciel billetterie en ligne"
|
||||
href="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||
class="weezevent-widget-integration"
|
||||
target="_blank"
|
||||
data-src="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||
data-width="650"
|
||||
data-height="600"
|
||||
data-resize="1"
|
||||
data-nopb="0"
|
||||
data-type="neo"
|
||||
data-width_auto="1"
|
||||
data-noscroll="0"
|
||||
data-id="1211855">
|
||||
Billetterie Weezevent
|
||||
</a>
|
||||
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>
|
138
eboutic/tests/test_billing_info.py
Normal file
138
eboutic/tests/test_billing_info.py
Normal file
@ -0,0 +1,138 @@
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from model_bakery import baker
|
||||
from pytest_django.asserts import assertRedirects
|
||||
|
||||
from core.baker_recipes import subscriber_user
|
||||
from core.models import User
|
||||
from counter.models import BillingInfo
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestBillingInfo:
|
||||
@pytest.fixture
|
||||
def payload(self):
|
||||
return {
|
||||
"first_name": "Subscribed",
|
||||
"last_name": "User",
|
||||
"address_1": "3, rue de Troyes",
|
||||
"zip_code": "34301",
|
||||
"city": "Sète",
|
||||
"country": "FR",
|
||||
"phone_number": "0612345678",
|
||||
}
|
||||
|
||||
def test_not_authorized(self, client: Client, payload: dict[str, str]):
|
||||
response = client.post(
|
||||
reverse("eboutic:billing_infos"),
|
||||
payload,
|
||||
)
|
||||
assertRedirects(
|
||||
response,
|
||||
reverse("core:login", query={"next": reverse("eboutic:billing_infos")}),
|
||||
)
|
||||
|
||||
def test_edit_infos(self, client: Client, payload: dict[str, str]):
|
||||
user = subscriber_user.make()
|
||||
baker.make(BillingInfo, customer=user.customer)
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
reverse("eboutic:billing_infos"),
|
||||
payload,
|
||||
)
|
||||
user.refresh_from_db()
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert response.status_code == 302
|
||||
assert hasattr(user.customer, "billing_infos")
|
||||
assert infos.customer == user.customer
|
||||
for key, val in payload.items():
|
||||
assert getattr(infos, key) == val
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"user_maker", [subscriber_user.make, lambda: baker.make(User)]
|
||||
)
|
||||
def test_create_infos(
|
||||
self, client: Client, user_maker: Callable[[], User], payload: dict[str, str]
|
||||
):
|
||||
user = user_maker()
|
||||
client.force_login(user)
|
||||
assert not BillingInfo.objects.filter(customer__user=user).exists()
|
||||
response = client.post(
|
||||
reverse("eboutic:billing_infos"),
|
||||
payload,
|
||||
)
|
||||
assert response.status_code == 302
|
||||
user.refresh_from_db()
|
||||
assert hasattr(user, "customer")
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert hasattr(user.customer, "billing_infos")
|
||||
assert infos.customer == user.customer
|
||||
for key, val in payload.items():
|
||||
assert getattr(infos, key) == val
|
||||
|
||||
def test_invalid_data(self, client: Client, payload: dict[str, str]):
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
# address_1, zip_code and country are missing
|
||||
del payload["city"]
|
||||
response = client.post(
|
||||
reverse("eboutic:billing_infos"),
|
||||
payload,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
user.customer.refresh_from_db()
|
||||
assert not hasattr(user.customer, "billing_infos")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"phone_number",
|
||||
["+33612345678", "0612345678", "06 12 34 56 78", "06-12-34-56-78"],
|
||||
)
|
||||
def test_phone_number_format(
|
||||
self, client: Client, payload: dict[str, str], phone_number: str
|
||||
):
|
||||
"""Test that various formats of phone numbers are accepted."""
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
payload["phone_number"] = phone_number
|
||||
response = client.post(
|
||||
reverse("eboutic:billing_infos"),
|
||||
payload,
|
||||
)
|
||||
assert response.status_code == 302
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert infos.phone_number == "0612345678"
|
||||
assert infos.phone_number.country_code == 33
|
||||
|
||||
def test_foreign_phone_number(self, client: Client, payload: dict[str, str]):
|
||||
"""Test that a foreign phone number is accepted."""
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
payload["phone_number"] = "+49612345678"
|
||||
response = client.post(
|
||||
reverse("eboutic:billing_infos"),
|
||||
payload,
|
||||
)
|
||||
assert response.status_code == 302
|
||||
infos = BillingInfo.objects.get(customer__user=user)
|
||||
assert infos.phone_number.as_national == "06123 45678"
|
||||
assert infos.phone_number.country_code == 49
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"phone_number", ["061234567a", "06 12 34 56", "061234567879", "azertyuiop"]
|
||||
)
|
||||
def test_invalid_phone_number(
|
||||
self, client: Client, payload: dict[str, str], phone_number: str
|
||||
):
|
||||
"""Test that invalid phone numbers are rejected."""
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
payload["phone_number"] = phone_number
|
||||
response = client.post(
|
||||
reverse("eboutic:billing_infos"),
|
||||
payload,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert not BillingInfo.objects.filter(customer__user=user).exists()
|
@ -123,11 +123,11 @@ class TestEboutic(TestCase):
|
||||
assert response.status_code == 200
|
||||
self.assertInHTML(
|
||||
"<tr><td>Cotis 2 semestres</td><td>1</td><td>28.00 €</td></tr>",
|
||||
response.content.decode(),
|
||||
response.text,
|
||||
)
|
||||
self.assertInHTML(
|
||||
"<tr><td>Barbar</td><td>3</td><td>1.70 €</td></tr>",
|
||||
response.content.decode(),
|
||||
response.text,
|
||||
)
|
||||
assert "basket_id" in self.client.session
|
||||
basket = Basket.objects.get(id=self.client.session["basket_id"])
|
||||
@ -178,7 +178,7 @@ class TestEboutic(TestCase):
|
||||
response = self.client.get(reverse("eboutic:command"))
|
||||
self.assertInHTML(
|
||||
"<tr><td>Cotis 2 semestres</td><td>1</td><td>28.00 €</td></tr>",
|
||||
response.content.decode(),
|
||||
response.text,
|
||||
)
|
||||
basket = Basket.objects.get(id=self.client.session["basket_id"])
|
||||
assert basket.items.count() == 1
|
||||
@ -206,7 +206,7 @@ class TestEboutic(TestCase):
|
||||
url = self.generate_bank_valid_answer()
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.content.decode() == "Payment successful"
|
||||
assert response.text == "Payment successful"
|
||||
new_balance = Customer.objects.get(user=self.subscriber).amount
|
||||
assert new_balance == initial_balance + 15
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
||||
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
#
|
||||
@ -26,9 +26,10 @@ from django.urls import path, register_converter
|
||||
|
||||
from eboutic.converters import PaymentResultConverter
|
||||
from eboutic.views import (
|
||||
BillingInfoFormFragment,
|
||||
EbouticCommand,
|
||||
EtransactionAutoAnswer,
|
||||
e_transaction_data,
|
||||
EurokPartnerFragment,
|
||||
eboutic_main,
|
||||
pay_with_sith,
|
||||
payment_result,
|
||||
@ -40,9 +41,10 @@ urlpatterns = [
|
||||
# Subscription views
|
||||
path("", eboutic_main, name="main"),
|
||||
path("command/", EbouticCommand.as_view(), name="command"),
|
||||
path("billing-infos/", BillingInfoFormFragment.as_view(), name="billing_infos"),
|
||||
path("pay/sith/", pay_with_sith, name="pay_with_sith"),
|
||||
path("pay/<res:result>/", payment_result, name="payment_result"),
|
||||
path("et_data/", e_transaction_data, name="et_data"),
|
||||
path("eurok/", EurokPartnerFragment.as_view(), name="eurok"),
|
||||
path(
|
||||
"et_autoanswer",
|
||||
EtransactionAutoAnswer.as_view(),
|
||||
|
125
eboutic/views.py
125
eboutic/views.py
@ -13,10 +13,12 @@
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import contextlib
|
||||
import json
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
@ -25,24 +27,33 @@ from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||
from cryptography.hazmat.primitives.hashes import SHA1
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import (
|
||||
LoginRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.db import DatabaseError, transaction
|
||||
from django.db.utils import cached_property
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from django.views.generic import TemplateView, View
|
||||
from django.views.generic import TemplateView, UpdateView, View
|
||||
from django_countries.fields import Country
|
||||
|
||||
from core.auth.mixins import IsSubscriberMixin
|
||||
from core.views.mixins import FragmentMixin, UseFragmentsMixin
|
||||
from counter.forms import BillingInfoForm
|
||||
from counter.models import Counter, Customer, Product
|
||||
from counter.models import BillingInfo, Counter, Customer, Product
|
||||
from eboutic.forms import BasketForm
|
||||
from eboutic.models import (
|
||||
Basket,
|
||||
BasketItem,
|
||||
BillingInfoState,
|
||||
Invoice,
|
||||
InvoiceItem,
|
||||
get_eboutic_products,
|
||||
@ -51,6 +62,7 @@ from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||
from django.utils.html import SafeString
|
||||
|
||||
|
||||
@login_required
|
||||
@ -88,15 +100,78 @@ def payment_result(request, result: str) -> HttpResponse:
|
||||
return render(request, "eboutic/eboutic_payment_result.jinja", context)
|
||||
|
||||
|
||||
class BillingInfoState(Enum):
|
||||
VALID = 1
|
||||
EMPTY = 2
|
||||
MISSING_PHONE_NUMBER = 3
|
||||
class EurokPartnerFragment(IsSubscriberMixin, TemplateView):
|
||||
template_name = "eboutic/eurok_fragment.jinja"
|
||||
|
||||
|
||||
class EbouticCommand(LoginRequiredMixin, TemplateView):
|
||||
class BillingInfoFormFragment(
|
||||
LoginRequiredMixin, FragmentMixin, SuccessMessageMixin, UpdateView
|
||||
):
|
||||
"""Update billing info"""
|
||||
|
||||
model = BillingInfo
|
||||
form_class = BillingInfoForm
|
||||
template_name = "eboutic/eboutic_billing_info.jinja"
|
||||
success_message = _("Billing info registration success")
|
||||
|
||||
def get_initial(self):
|
||||
if self.object is None:
|
||||
return {
|
||||
"country": Country(code="FR"),
|
||||
}
|
||||
return {}
|
||||
|
||||
def render_fragment(self, request, **kwargs) -> SafeString:
|
||||
self.object = self.get_object()
|
||||
return super().render_fragment(request, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def customer(self) -> Customer:
|
||||
return Customer.get_or_create(self.request.user)[0]
|
||||
|
||||
def form_valid(self, form: BillingInfoForm):
|
||||
form.instance.customer = self.customer
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
# if a BillingInfo already exists, this view will behave like an UpdateView
|
||||
# otherwise, it will behave like a CreateView.
|
||||
return getattr(self.customer, "billing_infos", None)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
kwargs["billing_infos_state"] = BillingInfoState.from_model(self.object)
|
||||
kwargs["action"] = reverse("eboutic:billing_infos")
|
||||
match BillingInfoState.from_model(self.object):
|
||||
case BillingInfoState.EMPTY:
|
||||
messages.warning(
|
||||
self.request,
|
||||
_(
|
||||
"You must fill your billing infos if you want to pay with your credit card"
|
||||
),
|
||||
)
|
||||
case BillingInfoState.MISSING_PHONE_NUMBER:
|
||||
messages.warning(
|
||||
self.request,
|
||||
_(
|
||||
"The Crédit Agricole changed its policy related to the billing "
|
||||
+ "information that must be provided in order to pay with a credit card. "
|
||||
+ "If you want to pay with your credit card, you must add a phone number "
|
||||
+ "to the data you already provided.",
|
||||
),
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
return self.request.path
|
||||
|
||||
|
||||
class EbouticCommand(LoginRequiredMixin, UseFragmentsMixin, TemplateView):
|
||||
template_name = "eboutic/eboutic_makecommand.jinja"
|
||||
basket: Basket
|
||||
fragments = {
|
||||
"billing_infos_form": BillingInfoFormFragment,
|
||||
}
|
||||
|
||||
@method_decorator(login_required)
|
||||
def post(self, request, *args, **kwargs):
|
||||
@ -134,42 +209,22 @@ class EbouticCommand(LoginRequiredMixin, TemplateView):
|
||||
return super().get(request)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
default_billing_info = None
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
if hasattr(self.request.user, "customer"):
|
||||
customer = self.request.user.customer
|
||||
kwargs["customer_amount"] = customer.amount
|
||||
if hasattr(customer, "billing_infos"):
|
||||
default_billing_info = customer.billing_infos
|
||||
else:
|
||||
kwargs["customer_amount"] = None
|
||||
# make the enum available in the template
|
||||
kwargs["BillingInfoState"] = BillingInfoState
|
||||
if default_billing_info is None:
|
||||
kwargs["billing_infos_state"] = BillingInfoState.EMPTY
|
||||
elif default_billing_info.phone_number is None:
|
||||
kwargs["billing_infos_state"] = BillingInfoState.MISSING_PHONE_NUMBER
|
||||
else:
|
||||
kwargs["billing_infos_state"] = BillingInfoState.VALID
|
||||
if kwargs["billing_infos_state"] == BillingInfoState.VALID:
|
||||
# the user has already filled all of its billing_infos, thus we can
|
||||
# get it without expecting an error
|
||||
kwargs["billing_infos"] = dict(self.basket.get_e_transaction_data())
|
||||
kwargs["basket"] = self.basket
|
||||
kwargs["billing_form"] = BillingInfoForm(instance=default_billing_info)
|
||||
kwargs["billing_infos"] = {}
|
||||
|
||||
with contextlib.suppress(BillingInfo.DoesNotExist):
|
||||
kwargs["billing_infos"] = json.dumps(
|
||||
dict(self.basket.get_e_transaction_data())
|
||||
)
|
||||
return kwargs
|
||||
|
||||
|
||||
@login_required
|
||||
@require_GET
|
||||
def e_transaction_data(request):
|
||||
basket = Basket.from_session(request.session)
|
||||
if basket is None:
|
||||
return HttpResponse(status=404, content=json.dumps({"data": []}))
|
||||
data = basket.get_e_transaction_data()
|
||||
data = {"data": [{"key": key, "value": val} for key, val in data]}
|
||||
return HttpResponse(status=200, content=json.dumps(data))
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def pay_with_sith(request):
|
||||
|
@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-08 16:20+0200\n"
|
||||
"POT-Creation-Date: 2025-04-14 01:16+0200\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@ -3770,6 +3770,15 @@ msgstr "panier"
|
||||
msgid "invoice"
|
||||
msgstr "facture"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_billing_info.jinja
|
||||
msgid "Billing information"
|
||||
msgstr "Informations de facturation"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_billing_info.jinja
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid "Validate"
|
||||
msgstr "Valider"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
|
||||
msgid "Current account amount: "
|
||||
@ -3784,11 +3793,6 @@ msgstr "Valeur du panier : "
|
||||
msgid "Clear"
|
||||
msgstr "Vider"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
|
||||
msgid "Validate"
|
||||
msgstr "Valider"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid ""
|
||||
"You have not filled in your date of birth. As a result, you may not have "
|
||||
@ -3808,6 +3812,25 @@ msgstr "cette page"
|
||||
msgid "Eurockéennes 2025 partnership"
|
||||
msgstr "Partenariat Eurockéennes 2025"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid ""
|
||||
"Our partner uses Weezevent to sell tickets. Weezevent may collect user info "
|
||||
"according to it's own privacy policy. By clicking the accept button you "
|
||||
"consent to their terms of services."
|
||||
msgstr ""
|
||||
"Notre partenaire utilises Wezevent pour vendre ses billets. Weezevent peut collecter des informatinos utilisateur "
|
||||
"conformément à sa propre politique de confidentialité. En cliquant sur le bouton d'acceptation vous "
|
||||
"consentez à leurs termes de service."
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid "Privacy policy"
|
||||
msgstr "Politique de confidentialité"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
#: trombi/templates/trombi/comment_moderation.jinja
|
||||
msgid "Accept"
|
||||
msgstr "Accepter"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid ""
|
||||
"You must be subscribed to benefit from the partnership with the Eurockéennes."
|
||||
@ -3837,29 +3860,6 @@ msgstr "État du panier"
|
||||
msgid "Remaining account amount: "
|
||||
msgstr "Solde restant : "
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
|
||||
msgid "Billing information"
|
||||
msgstr "Informations de facturation"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
|
||||
msgid ""
|
||||
"You must fill your billing infos if you want to pay with your credit card"
|
||||
msgstr ""
|
||||
"Vous devez renseigner vos coordonnées de facturation si vous voulez payer "
|
||||
"par carte bancaire"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
|
||||
msgid ""
|
||||
"The Crédit Agricole changed its policy related to the billing information "
|
||||
"that must be provided in order to pay with a credit card. If you want to pay "
|
||||
"with your credit card, you must add a phone number to the data you already "
|
||||
"provided."
|
||||
msgstr ""
|
||||
"Le Crédit Agricole a changé sa politique relative aux informations à "
|
||||
"fournir pour effectuer un paiement par carte bancaire. De ce fait, si vous "
|
||||
"souhaitez payer par carte, vous devez rajouter un numéro de téléphone aux "
|
||||
"données que vous aviez déjà fourni."
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja
|
||||
msgid "Pay with credit card"
|
||||
msgstr "Payer avec une carte bancaire"
|
||||
@ -3893,6 +3893,29 @@ msgstr "Le paiement a échoué"
|
||||
msgid "Return to eboutic"
|
||||
msgstr "Retourner à l'eboutic"
|
||||
|
||||
#: eboutic/views.py
|
||||
msgid "Billing info registration success"
|
||||
msgstr "Informations de facturation enregistrées"
|
||||
|
||||
#: eboutic/views.py
|
||||
msgid ""
|
||||
"You must fill your billing infos if you want to pay with your credit card"
|
||||
msgstr ""
|
||||
"Vous devez renseigner vos coordonnées de facturation si vous voulez payer "
|
||||
"par carte bancaire"
|
||||
|
||||
#: eboutic/views.py
|
||||
msgid ""
|
||||
"The Crédit Agricole changed its policy related to the billing information "
|
||||
"that must be provided in order to pay with a credit card. If you want to pay "
|
||||
"with your credit card, you must add a phone number to the data you already "
|
||||
"provided."
|
||||
msgstr ""
|
||||
"Le Crédit Agricole a changé sa politique relative aux informations à "
|
||||
"fournir pour effectuer un paiement par carte bancaire. De ce fait, si vous "
|
||||
"souhaitez payer par carte, vous devez rajouter un numéro de téléphone aux "
|
||||
"données que vous aviez déjà fourni."
|
||||
|
||||
#: election/models.py
|
||||
msgid "start candidature"
|
||||
msgstr "début des candidatures"
|
||||
@ -5382,10 +5405,6 @@ msgstr "fin"
|
||||
msgid "Moderate Trombi comments"
|
||||
msgstr "Modérer les commentaires du Trombi"
|
||||
|
||||
#: trombi/templates/trombi/comment_moderation.jinja
|
||||
msgid "Accept"
|
||||
msgstr "Accepter"
|
||||
|
||||
#: trombi/templates/trombi/comment_moderation.jinja
|
||||
msgid "Reject"
|
||||
msgstr "Refuser"
|
||||
|
@ -7,7 +7,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-09 23:23+0200\n"
|
||||
"POT-Creation-Date: 2025-04-13 00:18+0200\n"
|
||||
"PO-Revision-Date: 2024-09-17 11:54+0200\n"
|
||||
"Last-Translator: Sli <antoine@bartuccio.fr>\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@ -247,18 +247,6 @@ msgstr "Types de produits réordonnés !"
|
||||
msgid "Product type reorganisation failed with status code : %d"
|
||||
msgstr "La réorganisation des types de produit a échoué avec le code : %d"
|
||||
|
||||
#: eboutic/static/bundled/eboutic/makecommand-index.ts
|
||||
msgid "Incorrect value"
|
||||
msgstr "Valeur incorrecte"
|
||||
|
||||
#: eboutic/static/bundled/eboutic/makecommand-index.ts
|
||||
msgid "Billing info registration success"
|
||||
msgstr "Informations de facturation enregistrées"
|
||||
|
||||
#: eboutic/static/bundled/eboutic/makecommand-index.ts
|
||||
msgid "Billing info registration failure"
|
||||
msgstr "Echec de l'enregistrement des informations de facturation."
|
||||
|
||||
#: sas/static/bundled/sas/pictures-download-index.ts
|
||||
msgid "pictures.%(extension)s"
|
||||
msgstr "photos.%(extension)s"
|
||||
|
@ -111,7 +111,9 @@ class TestUVCreation(TestCase):
|
||||
def test_create_uv_unauthorized_fail(self):
|
||||
# Test with anonymous user
|
||||
response = self.client.post(self.create_uv_url, create_uv_template(0))
|
||||
assertRedirects(response, reverse("core:login") + f"?next={self.create_uv_url}")
|
||||
assertRedirects(
|
||||
response, reverse("core:login", query={"next": self.create_uv_url})
|
||||
)
|
||||
|
||||
# Test with subscribed user
|
||||
self.client.force_login(self.sli)
|
||||
@ -194,7 +196,9 @@ class TestUVDelete(TestCase):
|
||||
def test_uv_delete_pedagogy_unauthorized_fail(self):
|
||||
# Anonymous user
|
||||
response = self.client.post(self.delete_uv_url)
|
||||
assertRedirects(response, reverse("core:login") + f"?next={self.delete_uv_url}")
|
||||
assertRedirects(
|
||||
response, reverse("core:login", query={"next": self.delete_uv_url})
|
||||
)
|
||||
assert UV.objects.filter(pk=self.uv.pk).exists()
|
||||
|
||||
for user in baker.make(User), subscriber_user.make():
|
||||
@ -248,7 +252,9 @@ class TestUVUpdate(TestCase):
|
||||
response = self.client.post(
|
||||
self.update_uv_url, create_uv_template(self.bibou.id, code="PA00")
|
||||
)
|
||||
assertRedirects(response, reverse("core:login") + f"?next={self.update_uv_url}")
|
||||
assertRedirects(
|
||||
response, reverse("core:login", query={"next": self.update_uv_url})
|
||||
)
|
||||
|
||||
# Not subscribed user
|
||||
self.client.force_login(self.guy)
|
||||
@ -337,7 +343,7 @@ class TestUVCommentCreationAndDisplay(TestCase):
|
||||
nb_comments = self.uv.comments.count()
|
||||
# Test with anonymous user
|
||||
response = self.client.post(self.uv_url, create_uv_comment_template(0))
|
||||
assertRedirects(response, reverse("core:login") + f"?next={self.uv_url}")
|
||||
assertRedirects(response, reverse("core:login", query={"next": self.uv_url}))
|
||||
|
||||
# Test with non subscribed user
|
||||
self.client.force_login(self.guy)
|
||||
@ -425,7 +431,7 @@ class TestUVCommentDelete(TestCase):
|
||||
|
||||
# Anonymous user
|
||||
response = self.client.post(url)
|
||||
assertRedirects(response, reverse("core:login") + f"?next={url}")
|
||||
assertRedirects(response, reverse("core:login", query={"next": url}))
|
||||
|
||||
# Unsbscribed user
|
||||
for user in baker.make(User), subscriber_user.make():
|
||||
@ -485,7 +491,7 @@ class TestUVCommentUpdate(TestCase):
|
||||
url = reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id})
|
||||
# Anonymous user
|
||||
response = self.client.post(url, self.comment_edit)
|
||||
assertRedirects(response, reverse("core:login") + f"?next={url}")
|
||||
assertRedirects(response, reverse("core:login", query={"next": url}))
|
||||
|
||||
# Unsbscribed user
|
||||
self.client.force_login(baker.make(User))
|
||||
@ -569,7 +575,7 @@ class TestUVModerationForm(TestCase):
|
||||
url = reverse("pedagogy:moderation")
|
||||
# Test with anonymous user
|
||||
response = self.client.get(url)
|
||||
assertRedirects(response, reverse("core:login") + f"?next={url}")
|
||||
assertRedirects(response, reverse("core:login", query={"next": url}))
|
||||
|
||||
# Test with unsubscribed user
|
||||
self.client.force_login(self.guy)
|
||||
@ -776,7 +782,7 @@ class TestUVCommentReportCreate(TestCase):
|
||||
response = self.client.post(
|
||||
url, {"comment": self.comment.id, "reporter": 0, "reason": "C'est moche"}
|
||||
)
|
||||
assertRedirects(response, reverse("core:login") + f"?next={url}")
|
||||
assertRedirects(response, reverse("core:login", query={"next": url}))
|
||||
assert not UVCommentReport.objects.all().exists()
|
||||
|
||||
def test_notifications(self):
|
||||
|
@ -4,30 +4,30 @@ version = "3"
|
||||
description = "Le web Sith de l'AE"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "Skia", email = "skia@hya.sk"},
|
||||
{name = "klmp200", email = "antoine@bartuccio.fr"},
|
||||
{name = "Krophil", email = "pierre.brunet@krophil.fr"},
|
||||
{name = "Maréchal", email = "thgirod@hotmail.com"},
|
||||
{name = "Och", email = "francescowitz68@gmail.com"},
|
||||
{name = "tleb", email = "tleb@openmailbox.org"},
|
||||
{name = "Soldat", email = "ryan-68@live.fr"},
|
||||
{name = "Nabos", email = "gnikwo@hotmail.com"},
|
||||
{name = "Terre", email = "jbaptiste.lenglet+git@gmail.com"},
|
||||
{name = "Lo-J", email = "renaudg779@gmail.com"},
|
||||
{name = "Vial", email = "robin.trioux@utbm.fr"},
|
||||
{ name = "Skia", email = "skia@hya.sk" },
|
||||
{ name = "klmp200", email = "antoine@bartuccio.fr" },
|
||||
{ name = "Krophil", email = "pierre.brunet@krophil.fr" },
|
||||
{ name = "Maréchal", email = "thgirod@hotmail.com" },
|
||||
{ name = "Och", email = "francescowitz68@gmail.com" },
|
||||
{ name = "tleb", email = "tleb@openmailbox.org" },
|
||||
{ name = "Soldat", email = "ryan-68@live.fr" },
|
||||
{ name = "Nabos", email = "gnikwo@hotmail.com" },
|
||||
{ name = "Terre", email = "jbaptiste.lenglet+git@gmail.com" },
|
||||
{ name = "Lo-J", email = "renaudg779@gmail.com" },
|
||||
{ name = "Vial", email = "robin.trioux@utbm.fr" },
|
||||
]
|
||||
license = {text = "GPL-3.0-only"}
|
||||
license = { text = "GPL-3.0-only" }
|
||||
requires-python = "<4.0,>=3.12"
|
||||
dependencies = [
|
||||
"django<5.0.0,>=4.2.20",
|
||||
"django-ninja<2.0.0,>=1.3.0",
|
||||
"django-ninja-extra<1.0.0,>=0.22.4",
|
||||
"django<6.0.0,>=5.2.0",
|
||||
"django-ninja<2.0.0,>=1.4.0",
|
||||
"django-ninja-extra<1.0.0,>=0.22.9",
|
||||
"Pillow<12.0.0,>=11.1.0",
|
||||
"mistune<4.0.0,>=3.1.2",
|
||||
"mistune<4.0.0,>=3.1.3",
|
||||
"django-jinja<3.0.0,>=2.11.0",
|
||||
"cryptography<45.0.0,>=44.0.2",
|
||||
"django-phonenumber-field<9.0.0,>=8.0.0",
|
||||
"phonenumbers>=9.0.0,<10.0.0",
|
||||
"django-phonenumber-field<9.0.0,>=8.1.0",
|
||||
"phonenumbers>=9.0.2,<10.0.0",
|
||||
"reportlab<5.0.0,>=4.3.1",
|
||||
"django-haystack<4.0.0,>=3.3.0",
|
||||
"xapian-haystack<4.0.0,>=3.1.0",
|
||||
@ -35,17 +35,17 @@ dependencies = [
|
||||
"django-ordered-model<4.0.0,>=3.7.4",
|
||||
"django-simple-captcha<1.0.0,>=0.6.2",
|
||||
"python-dateutil<3.0.0.0,>=2.9.0.post0",
|
||||
"sentry-sdk<3.0.0,>=2.22.0",
|
||||
"sentry-sdk<3.0.0,>=2.25.1",
|
||||
"jinja2<4.0.0,>=3.1.6",
|
||||
"django-countries<8.0.0,>=7.6.1",
|
||||
"dict2xml<2.0.0,>=1.7.6",
|
||||
"Sphinx<6,>=5",
|
||||
"tomli<3.0.0,>=2.2.1",
|
||||
"django-honeypot<2.0.0,>=1.2.1",
|
||||
"pydantic-extra-types<3.0.0,>=2.10.2",
|
||||
"django-honeypot",
|
||||
"pydantic-extra-types<3.0.0,>=2.10.3",
|
||||
"ical<10.0.0,>=9.1.0",
|
||||
"redis[hiredis]<6.0.0,>=5.2.0",
|
||||
"environs[django]<15.0.0,>=14.1.0",
|
||||
"redis[hiredis]<6.0.0,>=5.2.1",
|
||||
"environs[django]<15.0.0,>=14.1.1",
|
||||
"requests>=2.32.3",
|
||||
"honcho>=2.0.0",
|
||||
"psutil>=7.0.0",
|
||||
@ -57,13 +57,13 @@ documentation = "https://sith-ae.readthedocs.io/"
|
||||
|
||||
[dependency-groups]
|
||||
prod = [
|
||||
"psycopg[c]<4.0.0,>=3.2.3",
|
||||
"psycopg[c]<4.0.0,>=3.2.6",
|
||||
]
|
||||
dev = [
|
||||
"django-debug-toolbar<5.0.0,>=4.4.6",
|
||||
"ipython<10.0.0,>=9.0.2",
|
||||
"pre-commit<5.0.0,>=4.1.0",
|
||||
"ruff<1.0.0,>=0.9.10",
|
||||
"ruff<1.0.0,>=0.11.1",
|
||||
"djhtml<4.0.0,>=3.0.7",
|
||||
"faker<38.0.0,>=37.0.0",
|
||||
"rjsmin<2.0.0,>=1.2.4",
|
||||
@ -88,6 +88,9 @@ docs = [
|
||||
[tool.uv]
|
||||
default-groups = ["dev", "tests", "docs"]
|
||||
|
||||
[tool.uv.sources]
|
||||
django-honeypot = { git = "https://github.com/jamesturk/django-honeypot.git", rev = "3986228" }
|
||||
|
||||
[tool.xapian]
|
||||
version = "1.4.25"
|
||||
|
||||
|
@ -46,7 +46,9 @@ class AlbumController(ControllerBase):
|
||||
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||
def fetch_album(self, filters: Query[AlbumFilterSchema]):
|
||||
"""General-purpose album search."""
|
||||
return filters.filter(Album.objects.viewable_by(self.context.request.user))
|
||||
return filters.filter(
|
||||
Album.objects.viewable_by(self.context.request.user).order_by("-date")
|
||||
)
|
||||
|
||||
@route.get(
|
||||
"/autocomplete-search",
|
||||
@ -63,7 +65,9 @@ class AlbumController(ControllerBase):
|
||||
If you don't need the path of the albums,
|
||||
do NOT use this route.
|
||||
"""
|
||||
return filters.filter(Album.objects.viewable_by(self.context.request.user))
|
||||
return filters.filter(
|
||||
Album.objects.viewable_by(self.context.request.user).order_by("-date")
|
||||
)
|
||||
|
||||
|
||||
@api_controller("/sas/picture")
|
||||
|
@ -169,7 +169,7 @@ class TestSasModeration(TestCase):
|
||||
assertInHTML(
|
||||
'<ul class="errorlist nonfield"><li>'
|
||||
"Vous avez déjà déposé une demande de retrait pour cette photo.</li></ul>",
|
||||
res.content.decode(),
|
||||
res.text,
|
||||
)
|
||||
|
||||
|
||||
|
@ -110,7 +110,6 @@ INSTALLED_APPS = (
|
||||
"core",
|
||||
"club",
|
||||
"subscription",
|
||||
"accounting",
|
||||
"counter",
|
||||
"eboutic",
|
||||
"launderette",
|
||||
|
458
uv.lock
generated
458
uv.lock
generated
@ -1,4 +1,5 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.12, <4.0"
|
||||
|
||||
[[package]]
|
||||
@ -208,41 +209,41 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.12"
|
||||
version = "7.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994 },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -331,16 +332,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "4.2.20"
|
||||
version = "5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/dd/33d2a11713f6b78493273a32d99bb449f2d93663ed4ec15a8b890d44ba04/Django-4.2.20.tar.gz", hash = "sha256:92bac5b4432a64532abb73b2ac27203f485e40225d2640a7fbef2b62b876e789", size = 10432686 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/1b/c6da718c65228eb3a7ff7ba6a32d8e80fa840ca9057490504e099e4dd1ef/Django-5.2.tar.gz", hash = "sha256:1a47f7a7a3d43ce64570d350e008d2949abe8c7e21737b351b6a1611277c6d89", size = 10824891 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/5d/7571ba1c288ead056dda7adad46b25cbf64790576f095565282e996138b1/Django-4.2.20-py3-none-any.whl", hash = "sha256:213381b6e4405f5c8703fffc29cd719efdf189dec60c67c04f76272b3dc845b9", size = 7993584 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/e0/6a5b5ea350c5bd63fe94b05e4c146c18facb51229d9dee42aa39f9fc2214/Django-5.2-py3-none-any.whl", hash = "sha256:91ceed4e3a6db5aedced65e3c8f963118ea9ba753fc620831c77074e620e7d83", size = 8301361 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -391,14 +392,10 @@ sdist = { url = "https://files.pythonhosted.org/packages/b4/09/623634ca5f8b9fe99
|
||||
[[package]]
|
||||
name = "django-honeypot"
|
||||
version = "1.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
source = { git = "https://github.com/jamesturk/django-honeypot.git?rev=3986228#39862286c61364d1456a8df7b7ac5892db72af04" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/8f/0bcf3f10ec08720086ab625dddfa061f8534cf06b16eb7567b0cc9242f4f/django_honeypot-1.2.1.tar.gz", hash = "sha256:ab5c2aad214d86def2f00f6a79aa14f171db7301ac8712f20dc21a83dd5d6413", size = 10185 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/c9/6372492315f3c219c81fb711769bc9522a9bbe90d96eed3114270eec6635/django_honeypot-1.2.1-py3-none-any.whl", hash = "sha256:fdabe4ded66b6db25d04af2446de3bf7cb047cb5db097a864c8c97b081ef736f", size = 10892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-jinja"
|
||||
@ -415,20 +412,20 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django-ninja"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/77/89ee4ebaa5151b7d85cebaf8d6ec0b9e5074326c3ad8259c763763306d51/django_ninja-1.3.0.tar.gz", hash = "sha256:5b320e2dc0f41a6032bfa7e1ebc33559ae1e911a426f0c6be6674a50b20819be", size = 3702324 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/a6/f8c158358e5e892839929c342872456b42d5e06c325aef27b3e8569b8932/django_ninja-1.4.0.tar.gz", hash = "sha256:590bb1c8fc32f07bac1d5b5d404419205c41b4945b86d4237b0676174c1b6df7", size = 3707937 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/72/fd2589323b40893d3224e174eeec0c4ce5a42c7d2d384d11ba269ad4d050/django_ninja-1.3.0-py3-none-any.whl", hash = "sha256:f58096b6c767d1403dfd6c49743f82d780d7b9688d9302ecab316ac1fa6131bb", size = 2423381 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/d0/defc86eff755cdf32c717b2ed7872a2b19da151b34e0749a9567ce7810f8/django_ninja-1.4.0-py3-none-any.whl", hash = "sha256:b64bb7f269d0c0a890b429802dffbe3689298eb1ed7dc324224f3b325e8f8871", size = 2425903 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-ninja-extra"
|
||||
version = "0.22.4"
|
||||
version = "0.22.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
@ -437,9 +434,9 @@ dependencies = [
|
||||
{ name = "django-ninja" },
|
||||
{ name = "injector" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/87/d6/ecd8b14a8e182226fd74b90901fb693ca09e5aa9ddd7b46bf035e175c47e/django_ninja_extra-0.22.4.tar.gz", hash = "sha256:55a99edd8134558a67d5c5e5ee6ceaf098d79dc4aa3fb2adad0f0948687bc6c5", size = 53280 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/c4/f4e4908351c6c052960c82988531b72ad82271322e7d9a5a3f01bd8a5854/django_ninja_extra-0.22.9.tar.gz", hash = "sha256:f4436e4bdff0f90a122c5cbab8a8c15f9c3528769259e373e9e513c3ef1f59f0", size = 53398 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/4f/c15e24973d7569a214c0f70c1698ac5dcfa60b8f0ddc5967bef6fb4b6e67/django_ninja_extra-0.22.4-py3-none-any.whl", hash = "sha256:d57b5f75754bfa900da5f597e2365215fac48b62f88d28e7aebab07cd360f52d", size = 72788 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/6a/ff3d00b3d42a930aff1f6c9048ca9b281b589b38046d437d444dc2470f06/django_ninja_extra-0.22.9-py3-none-any.whl", hash = "sha256:38a277eb35e25f470d641cd5c0a4fb76e78d975f12c1d50ddb61cc37a3477dfb", size = 72948 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -453,14 +450,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django-phonenumber-field"
|
||||
version = "8.0.0"
|
||||
version = "8.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/d1/0a2ba41434d98ac9a2669ea7edfc8d1bc75055da450d94894c597913e01c/django_phonenumber_field-8.0.0.tar.gz", hash = "sha256:8a560fe1b01b94c9de8cde22bc373b695f023cc6df4baba00264cb079da9f631", size = 43759 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/4b/edec2f3e4b8f8048540d7d1844bc576a0bdb711e1f6a4cde364c505c865d/django_phonenumber_field-8.1.0.tar.gz", hash = "sha256:f19450625574ad3a2de047c409527d0e57699496849e71e53b53e8679ff67b27", size = 43854 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ab/4738a40d6f297688ecedd08cf6dc1608ad06c08ba3c03d1fc85b19ac6717/django_phonenumber_field-8.0.0-py3-none-any.whl", hash = "sha256:196c917b70c01a98e327f482eb8a4a4a55a29891db551f99078585397370b3ba", size = 66441 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/98/0413e47d7d1fab0e5a81eda6caacdcba69d50f74f64b65febf943cbd1f1e/django_phonenumber_field-8.1.0-py3-none-any.whl", hash = "sha256:9ba66873bf47401b13fc57797fbf85e785864a6bfdc8b9daa3a9012886567045", size = 65208 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -532,23 +529,23 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "faker"
|
||||
version = "37.0.0"
|
||||
version = "37.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/c6/6820408cdd87c11f1fbbd2349b05bbda28174d746e6d708ad0f0a934f9d7/faker-37.0.0.tar.gz", hash = "sha256:d2e4e2a30d459a8ec0ae52a552aa51c48973cb32cf51107dee90f58a8322a880", size = 1875487 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/a6/b77f42021308ec8b134502343da882c0905d725a4d661c7adeaf7acaf515/faker-37.1.0.tar.gz", hash = "sha256:ad9dc66a3b84888b837ca729e85299a96b58fdaef0323ed0baace93c9614af06", size = 1875707 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/03/0ffcbc5ab352c266a648d029f79de54ca205c04661203d46a42e3f03492b/faker-37.0.0-py3-none-any.whl", hash = "sha256:2598f78b76710a4ed05e197dda5235be409b4c291ba5c9c7514989cfbc7a5144", size = 1918764 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/a1/8936bc8e79af80ca38288dd93ed44ed1f9d63beb25447a4c59e746e01f8d/faker-37.1.0-py3-none-any.whl", hash = "sha256:dc2f730be71cb770e9c715b13374d80dbcee879675121ab51f9683d262ae9a1c", size = 1918783 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.17.0"
|
||||
version = "3.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -577,14 +574,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "griffe"
|
||||
version = "1.6.0"
|
||||
version = "1.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a0/1a/d467b93f5e0ea4edf3c1caef44cfdd53a4a498cb3a6bb722df4dd0fdd66a/griffe-1.6.0.tar.gz", hash = "sha256:eb5758088b9c73ad61c7ac014f3cdfb4c57b5c2fcbfca69996584b702aefa354", size = 391819 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/59/08/7df7e90e34d08ad890bd71d7ba19451052f88dc3d2c483d228d1331a4736/griffe-1.7.2.tar.gz", hash = "sha256:98d396d803fab3b680c2608f300872fd57019ed82f0672f5b5323a9ad18c540c", size = 394919 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/02/5a22bc98d0aebb68c15ba70d2da1c84a5ef56048d79634e5f96cd2ba96e9/griffe-1.6.0-py3-none-any.whl", hash = "sha256:9f1dfe035d4715a244ed2050dfbceb05b1f470809ed4f6bb10ece5a7302f8dd1", size = 128470 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/5e/38b408f41064c9fcdbb0ea27c1bd13a1c8657c4846e04dab9f5ea770602c/griffe-1.7.2-py3-none-any.whl", hash = "sha256:1ed9c2e338a75741fc82083fe5a1bc89cb6142efe126194cc313e34ee6af5423", size = 129187 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -681,11 +678,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -699,7 +696,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "9.0.2"
|
||||
version = "9.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@ -713,9 +710,9 @@ dependencies = [
|
||||
{ name = "stack-data" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/ce/012a0f40ca58a966f87a6e894d6828e2817657cbdf522b02a5d3a87d92ce/ipython-9.0.2.tar.gz", hash = "sha256:ec7b479e3e5656bf4f58c652c120494df1820f4f28f522fb7ca09e213c2aab52", size = 4366102 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/9a/6b8984bedc990f3a4aa40ba8436dea27e23d26a64527de7c2e5e12e76841/ipython-9.1.0.tar.gz", hash = "sha256:a47e13a5e05e02f3b8e1e7a0f9db372199fe8c3763532fe7a1e0379e4e135f16", size = 4373688 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/3a/917cb9e72f4e1a4ea13c862533205ae1319bd664119189ee5cc9e4e95ebf/ipython-9.0.2-py3-none-any.whl", hash = "sha256:143ef3ea6fb1e1bffb4c74b114051de653ffb7737a3f7ab1670e657ca6ae8c44", size = 600524 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/9d/4ff2adf55d1b6e3777b0303fdbe5b723f76e46cba4a53a32fe82260d2077/ipython-9.1.0-py3-none-any.whl", hash = "sha256:2df07257ec2f84a6b346b8d83100bcf8fa501c6e01ab75cd3799b0bb253b3d2a", size = 604053 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -769,44 +766,44 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "5.3.1"
|
||||
version = "5.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/f6/c15ca8e5646e937c148e147244817672cf920b56ac0bf2cc1512ae674be8/lxml-5.3.1.tar.gz", hash = "sha256:106b7b5d2977b339f1e97efe2778e2ab20e99994cbb0ec5e55771ed0795920c8", size = 3678591 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/61/d3dc048cd6c7be6fe45b80cedcbdd4326ba4d550375f266d9f4246d0f4bc/lxml-5.3.2.tar.gz", hash = "sha256:773947d0ed809ddad824b7b14467e1a481b8976e87278ac4a730c2f7c7fcddc1", size = 3679948 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/f4/5121aa9ee8e09b8b8a28cf3709552efe3d206ca51a20d6fa471b60bb3447/lxml-5.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e69add9b6b7b08c60d7ff0152c7c9a6c45b4a71a919be5abde6f98f1ea16421c", size = 8191889 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/ca/8e9aa01edddc74878f4aea85aa9ab64372f46aa804d1c36dda861bf9eabf/lxml-5.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4e52e1b148867b01c05e21837586ee307a01e793b94072d7c7b91d2c2da02ffe", size = 4450685 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b3/ea40a5c98619fbd7e9349df7007994506d396b97620ced34e4e5053d3734/lxml-5.3.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4b382e0e636ed54cd278791d93fe2c4f370772743f02bcbe431a160089025c9", size = 5051722 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/5e/375418be35f8a695cadfe7e7412f16520e62e24952ed93c64c9554755464/lxml-5.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e49dc23a10a1296b04ca9db200c44d3eb32c8d8ec532e8c1fd24792276522a", size = 4786661 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7c/d258eaaa9560f6664f9b426a5165103015bee6512d8931e17342278bad0a/lxml-5.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4399b4226c4785575fb20998dc571bc48125dc92c367ce2602d0d70e0c455eb0", size = 5311766 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/bc/a041415be4135a1b3fdf017a5d873244cc16689456166fbdec4b27fba153/lxml-5.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5412500e0dc5481b1ee9cf6b38bb3b473f6e411eb62b83dc9b62699c3b7b79f7", size = 4836014 },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/88/047f24967d5e3fc97848ea2c207eeef0f16239cdc47368c8b95a8dc93a33/lxml-5.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c93ed3c998ea8472be98fb55aed65b5198740bfceaec07b2eba551e55b7b9ae", size = 4961064 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/b5/ecf5a20937ecd21af02c5374020f4e3a3538e10a32379a7553fca3d77094/lxml-5.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63d57fc94eb0bbb4735e45517afc21ef262991d8758a8f2f05dd6e4174944519", size = 4778341 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/05/56c359e07275911ed5f35ab1d63c8cd3360d395fb91e43927a2ae90b0322/lxml-5.3.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:b450d7cabcd49aa7ab46a3c6aa3ac7e1593600a1a0605ba536ec0f1b99a04322", size = 5345450 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/f4/f95e3ae12e9f32fbcde00f9affa6b0df07f495117f62dbb796a9a31c84d6/lxml-5.3.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:4df0ec814b50275ad6a99bc82a38b59f90e10e47714ac9871e1b223895825468", size = 4908336 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/f8/309546aec092434166a6e11c7dcecb5c2d0a787c18c072d61e18da9eba57/lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d184f85ad2bb1f261eac55cddfcf62a70dee89982c978e92b9a74a1bfef2e367", size = 4986049 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/1c/b951817cb5058ca7c332d012dfe8bc59dabd0f0a8911ddd7b7ea8e41cfbd/lxml-5.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b725e70d15906d24615201e650d5b0388b08a5187a55f119f25874d0103f90dd", size = 4860351 },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/23/45feba8dae1d35fcca1e51b051f59dc4223cbd23e071a31e25f3f73938a8/lxml-5.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a31fa7536ec1fb7155a0cd3a4e3d956c835ad0a43e3610ca32384d01f079ea1c", size = 5421580 },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/69/be245d7b2dbef81c542af59c97fcd641fbf45accf2dc1c325bae7d0d014c/lxml-5.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c3c8b55c7fc7b7e8877b9366568cc73d68b82da7fe33d8b98527b73857a225f", size = 5285778 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/06/128af2ed04bac99b8f83becfb74c480f1aa18407b5c329fad457e08a1bf4/lxml-5.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d61ec60945d694df806a9aec88e8f29a27293c6e424f8ff91c80416e3c617645", size = 5054455 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/2d/f03a21cf6cc75cdd083563e509c7b6b159d761115c4142abb5481094ed8c/lxml-5.3.1-cp312-cp312-win32.whl", hash = "sha256:f4eac0584cdc3285ef2e74eee1513a6001681fd9753b259e8159421ed28a72e5", size = 3486315 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9c/8abe21585d20ef70ad9cec7562da4332b764ed69ec29b7389d23dfabcea0/lxml-5.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:29bfc8d3d88e56ea0a27e7c4897b642706840247f59f4377d81be8f32aa0cfbf", size = 3816925 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/1c/724931daa1ace168e0237b929e44062545bf1551974102a5762c349c668d/lxml-5.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c093c7088b40d8266f57ed71d93112bd64c6724d31f0794c1e52cc4857c28e0e", size = 8171881 },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/0c/857b8fb6010c4246e66abeebb8639eaabba60a6d9b7c606554ecc5cbf1ee/lxml-5.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0884e3f22d87c30694e625b1e62e6f30d39782c806287450d9dc2fdf07692fd", size = 4440394 },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/72/c9e81de6a000f9682ccdd13503db26e973b24c68ac45a7029173237e3eed/lxml-5.3.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1637fa31ec682cd5760092adfabe86d9b718a75d43e65e211d5931809bc111e7", size = 5037860 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/26/942048c4b14835711b583b48cd7209bd2b5f0b6939ceed2381a494138b14/lxml-5.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a364e8e944d92dcbf33b6b494d4e0fb3499dcc3bd9485beb701aa4b4201fa414", size = 4782513 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/65/27792339caf00f610cc5be32b940ba1e3009b7054feb0c4527cebac228d4/lxml-5.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:779e851fd0e19795ccc8a9bb4d705d6baa0ef475329fe44a13cf1e962f18ff1e", size = 5305227 },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/e1/25f7aa434a4d0d8e8420580af05ea49c3e12db6d297cf5435ac0a054df56/lxml-5.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4393600915c308e546dc7003d74371744234e8444a28622d76fe19b98fa59d1", size = 4829846 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/ed/faf235e0792547d24f61ee1448159325448a7e4f2ab706503049d8e5df19/lxml-5.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:673b9d8e780f455091200bba8534d5f4f465944cbdd61f31dc832d70e29064a5", size = 4949495 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/e1/8f572ad9ed6039ba30f26dd4c2c58fb90f79362d2ee35ca3820284767672/lxml-5.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a570f6a99e96c457f7bec5ad459c9c420ee80b99eb04cbfcfe3fc18ec6423", size = 4773415 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/75/6b57166b9d1983dac8f28f354e38bff8d6bcab013a241989c4d54c72701b/lxml-5.3.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:71f31eda4e370f46af42fc9f264fafa1b09f46ba07bdbee98f25689a04b81c20", size = 5337710 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/71/4aa56e2daa83bbcc66ca27b5155be2f900d996f5d0c51078eaaac8df9547/lxml-5.3.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:42978a68d3825eaac55399eb37a4d52012a205c0c6262199b8b44fcc6fd686e8", size = 4897362 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/10/3fa2da152cd9b49332fd23356ed7643c9b74cad636ddd5b2400a9730d12b/lxml-5.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8b1942b3e4ed9ed551ed3083a2e6e0772de1e5e3aca872d955e2e86385fb7ff9", size = 4977795 },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/d2/e1da0f7b20827e7b0ce934963cb6334c1b02cf1bb4aecd218c4496880cb3/lxml-5.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85c4f11be9cf08917ac2a5a8b6e1ef63b2f8e3799cec194417e76826e5f1de9c", size = 4858104 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/35/063420e1b33d3308f5aa7fcbdd19ef6c036f741c9a7a4bd5dc8032486b27/lxml-5.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:231cf4d140b22a923b1d0a0a4e0b4f972e5893efcdec188934cc65888fd0227b", size = 5416531 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/83/93a6457d291d1e37adfb54df23498101a4701834258c840381dd2f6a030e/lxml-5.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5865b270b420eda7b68928d70bb517ccbe045e53b1a428129bb44372bf3d7dd5", size = 5273040 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/25/ad4ac8fac488505a2702656550e63c2a8db3a4fd63db82a20dad5689cecb/lxml-5.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7bebc2275016cddf3c997bf8a0f7044160714c64a9b83975670a04e6d2252", size = 5050951 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/74/f7d223c704c87e44b3d27b5e0dde173a2fcf2e89c0524c8015c2b3554876/lxml-5.3.1-cp313-cp313-win32.whl", hash = "sha256:d0751528b97d2b19a388b302be2a0ee05817097bab46ff0ed76feeec24951f78", size = 3485357 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/83/8c54533b3576f4391eebea88454738978669a6cad0d8e23266224007939d/lxml-5.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:91fb6a43d72b4f8863d21f347a9163eecbf36e76e2f51068d59cd004c506f332", size = 3814484 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/7e/c749257a7fabc712c4df57927b0f703507f316e9f2c7e3219f8f76d36145/lxml-5.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:16b3897691ec0316a1aa3c6585f61c8b7978475587c5b16fc1d2c28d283dc1b0", size = 8193212 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/50/17e985ba162c9f1ca119f4445004b58f9e5ef559ded599b16755e9bfa260/lxml-5.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8d4b34a0eeaf6e73169dcfd653c8d47f25f09d806c010daf074fba2db5e2d3f", size = 4451439 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/b5/4960ba0fcca6ce394ed4a2f89ee13083e7fcbe9641a91166e8e9792fedb1/lxml-5.3.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cd7a959396da425022e1e4214895b5cfe7de7035a043bcc2d11303792b67554", size = 5052146 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/d1/184b04481a5d1f5758916de087430752a7b229bddbd6c1d23405078c72bd/lxml-5.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cac5eaeec3549c5df7f8f97a5a6db6963b91639389cdd735d5a806370847732b", size = 4789082 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/75/1a19749d373e9a3d08861addccdf50c92b628c67074b22b8f3c61997cf5a/lxml-5.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b5f7d77334877c2146e7bb8b94e4df980325fab0a8af4d524e5d43cd6f789d", size = 5312300 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/00/9d165d4060d3f347e63b219fcea5c6a3f9193e9e2868c6801e18e5379725/lxml-5.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13f3495cfec24e3d63fffd342cc8141355d1d26ee766ad388775f5c8c5ec3932", size = 4836655 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/e9/06720a33cc155966448a19677f079100517b6629a872382d22ebd25e48aa/lxml-5.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e70ad4c9658beeff99856926fd3ee5fde8b519b92c693f856007177c36eb2e30", size = 4961795 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/57/4540efab2673de2904746b37ef7f74385329afd4643ed92abcc9ec6e00ca/lxml-5.3.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:507085365783abd7879fa0a6fa55eddf4bdd06591b17a2418403bb3aff8a267d", size = 4779791 },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/ad/6056edf6c9f4fa1d41e6fbdae52c733a4a257fd0d7feccfa26ae051bb46f/lxml-5.3.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:5bb304f67cbf5dfa07edad904732782cbf693286b9cd85af27059c5779131050", size = 5346807 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/fa/5be91fc91a18f3f705ea5533bc2210b25d738c6b615bf1c91e71a9b2f26b/lxml-5.3.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:3d84f5c093645c21c29a4e972b84cb7cf682f707f8706484a5a0c7ff13d7a988", size = 4909213 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/74/71bb96a3b5ae36b74e0402f4fa319df5559a8538577f8c57c50f1b57dc15/lxml-5.3.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:bdc13911db524bd63f37b0103af014b7161427ada41f1b0b3c9b5b5a9c1ca927", size = 4987694 },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/c2/3953a68b0861b2f97234b1838769269478ccf872d8ea7a26e911238220ad/lxml-5.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ec944539543f66ebc060ae180d47e86aca0188bda9cbfadff47d86b0dc057dc", size = 4862865 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/9a/52e48f7cfd5a5e61f44a77e679880580dfb4f077af52d6ed5dd97e3356fe/lxml-5.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:59d437cc8a7f838282df5a199cf26f97ef08f1c0fbec6e84bd6f5cc2b7913f6e", size = 5423383 },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/67/42fe1d489e4dcc0b264bef361aef0b929fbb2b5378702471a3043bc6982c/lxml-5.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e275961adbd32e15672e14e0cc976a982075208224ce06d149c92cb43db5b93", size = 5286864 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/e4/03b1d040ee3aaf2bd4e1c2061de2eae1178fe9a460d3efc1ea7ef66f6011/lxml-5.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:038aeb6937aa404480c2966b7f26f1440a14005cb0702078c173c028eca72c31", size = 5056819 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/b3/e2ec8a6378e4d87da3af9de7c862bcea7ca624fc1a74b794180c82e30123/lxml-5.3.2-cp312-cp312-win32.whl", hash = "sha256:3c2c8d0fa3277147bff180e3590be67597e17d365ce94beb2efa3138a2131f71", size = 3486177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/8a/6a08254b0bab2da9573735725caab8302a2a1c9b3818533b41568ca489be/lxml-5.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:77809fcd97dfda3f399102db1794f7280737b69830cd5c961ac87b3c5c05662d", size = 3817134 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/fe/904fd1b0ba4f42ed5a144fcfff7b8913181892a6aa7aeb361ee783d441f8/lxml-5.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:77626571fb5270ceb36134765f25b665b896243529eefe840974269b083e090d", size = 8173598 },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/e8/5e332877b3ce4e2840507b35d6dbe1cc33b17678ece945ba48d2962f8c06/lxml-5.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78a533375dc7aa16d0da44af3cf6e96035e484c8c6b2b2445541a5d4d3d289ee", size = 4441586 },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/f4/8fe2e6d8721803182fbce2325712e98f22dbc478126070e62731ec6d54a0/lxml-5.3.2-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6f62b2404b3f3f0744bbcabb0381c5fe186fa2a9a67ecca3603480f4846c585", size = 5038447 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/ac/fa63f86a1a4b1ba8b03599ad9e2f5212fa813223ac60bfe1155390d1cc0c/lxml-5.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea918da00091194526d40c30c4996971f09dacab032607581f8d8872db34fbf", size = 4783583 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/7a/08898541296a02c868d4acc11f31a5839d80f5b21d4a96f11d4c0fbed15e/lxml-5.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c35326f94702a7264aa0eea826a79547d3396a41ae87a70511b9f6e9667ad31c", size = 5305684 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/be/9a6d80b467771b90be762b968985d3de09e0d5886092238da65dac9c1f75/lxml-5.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3bef90af21d31c4544bc917f51e04f94ae11b43156356aff243cdd84802cbf2", size = 4830797 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/1c/493632959f83519802637f7db3be0113b6e8a4e501b31411fbf410735a75/lxml-5.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52fa7ba11a495b7cbce51573c73f638f1dcff7b3ee23697467dc063f75352a69", size = 4950302 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/13/01aa3b92a6b93253b90c061c7527261b792f5ae7724b420cded733bfd5d6/lxml-5.3.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ad131e2c4d2c3803e736bb69063382334e03648de2a6b8f56a878d700d4b557d", size = 4775247 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/4a/baeb09fbf5c84809e119c9cf8e2e94acec326a9b45563bf5ae45a234973b/lxml-5.3.2-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:00a4463ca409ceacd20490a893a7e08deec7870840eff33dc3093067b559ce3e", size = 5338824 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/c7/a05850f169ad783ed09740ac895e158b06d25fce4b13887a8ac92a84d61c/lxml-5.3.2-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:87e8d78205331cace2b73ac8249294c24ae3cba98220687b5b8ec5971a2267f1", size = 4899079 },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/48/18ca583aba5235582db0e933ed1af6540226ee9ca16c2ee2d6f504fcc34a/lxml-5.3.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bf6389133bb255e530a4f2f553f41c4dd795b1fbb6f797aea1eff308f1e11606", size = 4978041 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/55/6968ddc88554209d1dba0dca196360c629b3dfe083bc32a3370f9523a0c4/lxml-5.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b3709fc752b42fb6b6ffa2ba0a5b9871646d97d011d8f08f4d5b3ee61c7f3b2b", size = 4859761 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/52/d2d3baa1e0b7d04a729613160f1562f466fb1a0e45085a33acb0d6981a2b/lxml-5.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:abc795703d0de5d83943a4badd770fbe3d1ca16ee4ff3783d7caffc252f309ae", size = 5418209 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/50/6005b297ba5f858a113d6e81ccdb3a558b95a615772e7412d1f1cbdf22d7/lxml-5.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:98050830bb6510159f65d9ad1b8aca27f07c01bb3884ba95f17319ccedc4bcf9", size = 5274231 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/33/6f40c09a5f7d7e7fcb85ef75072e53eba3fbadbf23e4991ca069ab2b1abb/lxml-5.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ba465a91acc419c5682f8b06bcc84a424a7aa5c91c220241c6fd31de2a72bc6", size = 5051899 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/3a/673bc5c0d5fb6596ee2963dd016fdaefaed2c57ede82c7634c08cbda86c1/lxml-5.3.2-cp313-cp313-win32.whl", hash = "sha256:56a1d56d60ea1ec940f949d7a309e0bff05243f9bd337f585721605670abb1c1", size = 3485315 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/be/cab8dd33b0dbe3af5b5d4d24137218f79ea75d540f74eb7d8581195639e0/lxml-5.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:1a580dc232c33d2ad87d02c8a3069d47abbcdce974b9c9cc82a79ff603065dbe", size = 3814639 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -891,11 +888,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mistune"
|
||||
version = "3.1.2"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/f7/f6d06304c61c2a73213c0a4815280f70d985429cda26272f490e42119c1a/mistune-3.1.2.tar.gz", hash = "sha256:733bf018ba007e8b5f2d3a9eb624034f6ee26c4ea769a98ec533ee111d504dff", size = 94613 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/79/bda47f7dd7c3c55770478d6d02c9960c430b0cf1773b72366ff89126ea31/mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0", size = 94347 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/92/30b4e54c4d7c48c06db61595cffbbf4f19588ea177896f9b78f0fbe021fd/mistune-3.1.2-py3-none-any.whl", hash = "sha256:4b47731332315cdca99e0ded46fc0004001c1299ff773dfb48fbe1fd226de319", size = 53696 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/4d/23c4e4f09da849e127e9f123241946c23c1e30f45a88366879e064211815/mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9", size = 53410 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -965,7 +962,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.6.7"
|
||||
version = "9.6.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "babel" },
|
||||
@ -980,9 +977,9 @@ dependencies = [
|
||||
{ name = "pymdown-extensions" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/d7/93e19c9587e5f4ed25647890555d58cf484a4d412be7037dc17b9c9179d9/mkdocs_material-9.6.7.tar.gz", hash = "sha256:3e2c1fceb9410056c2d91f334a00cdea3215c28750e00c691c1e46b2a33309b4", size = 3947458 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/7e/c65e330e99daa5813e7594e57a09219ad041ed631604a72588ec7c11b34b/mkdocs_material-9.6.11.tar.gz", hash = "sha256:0b7f4a0145c5074cdd692e4362d232fb25ef5b23328d0ec1ab287af77cc0deff", size = 3951595 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/d3/12f22de41bdd9e576ddc459b38c651d68edfb840b32acaa1f46ae36845e3/mkdocs_material-9.6.7-py3-none-any.whl", hash = "sha256:8a159e45e80fcaadd9fbeef62cbf928569b93df954d4dc5ba76d46820caf7b47", size = 8696755 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/91/79a15a772151aca0d505f901f6bbd4b85ee1fe54100256a6702056bab121/mkdocs_material-9.6.11-py3-none-any.whl", hash = "sha256:47f21ef9cbf4f0ebdce78a2ceecaa5d413581a55141e4464902224ebbc0b1263", size = 8703720 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -996,7 +993,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings"
|
||||
version = "0.28.3"
|
||||
version = "0.29.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jinja2" },
|
||||
@ -1004,26 +1001,25 @@ dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
{ name = "mkdocs" },
|
||||
{ name = "mkdocs-autorefs" },
|
||||
{ name = "mkdocs-get-deps" },
|
||||
{ name = "pymdown-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e3/48/d134ffefd61349ac96161078d836c7e0c15062e01104327c9a5b23398a0f/mkdocstrings-0.28.3.tar.gz", hash = "sha256:c753516b1b6cee12d00bf9c28255e22c0d71f34c721ca668971fce885d846e0f", size = 104109 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/41/e8/d22922664a627a0d3d7ff4a6ca95800f5dde54f411982591b4621a76225d/mkdocstrings-0.29.1.tar.gz", hash = "sha256:8722f8f8c5cd75da56671e0a0c1bbed1df9946c0cef74794d6141b34011abd42", size = 1212686 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/47/5c/205e4991fad1fbfe78b0d1fcfcf85f55556bcc93a5d6d94c7935e8463b87/mkdocstrings-0.28.3-py3-none-any.whl", hash = "sha256:df5351ffd10477aa3c2ff5cdf17544b936477195436923660274d084a5c1359c", size = 35177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/14/22533a578bf8b187e05d67e2c1721ce10e3f526610eebaf7a149d557ea7a/mkdocstrings-0.29.1-py3-none-any.whl", hash = "sha256:37a9736134934eea89cbd055a513d40a020d87dfcae9e3052c2a6b8cd4af09b6", size = 1631075 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings-python"
|
||||
version = "1.16.3"
|
||||
version = "1.16.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "griffe" },
|
||||
{ name = "mkdocs-autorefs" },
|
||||
{ name = "mkdocstrings" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d2/b1/dbe9b7cd770b8e6e59e7cd2a409c8180c649653a0078b7658594b6e76db8/mkdocstrings_python-1.16.3.tar.gz", hash = "sha256:d0cdee60399b397e7cbb25075cac4aa7a27b34f75621eb1898dfebc98b88d600", size = 424073 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/44/c8/600c4201b6b9e72bab16802316d0c90ce04089f8e6bb5e064cd2a5abba7e/mkdocstrings_python-1.16.10.tar.gz", hash = "sha256:f9eedfd98effb612ab4d0ed6dd2b73aff6eba5215e0a65cea6d877717f75502e", size = 205771 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/58/1b14293589488a7be37c0fc2f2532541962b0853653d554fc42d349b00bd/mkdocstrings_python-1.16.3-py3-none-any.whl", hash = "sha256:17b4d7a1add16032dff4b2f54b51d889fbd5d2fa87c166b22fbc3a56d37ec75e", size = 449301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/37/19549c5e0179785308cc988a68e16aa7550e4e270ec8a9878334e86070c6/mkdocstrings_python-1.16.10-py3-none-any.whl", hash = "sha256:63bb9f01f8848a644bdb6289e86dc38ceddeaa63ecc2e291e3b2ca52702a6643", size = 124112 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1097,11 +1093,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "phonenumbers"
|
||||
version = "9.0.0"
|
||||
version = "9.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/96/58ab3aa4f8695c85f5dce60c15bb3b113856f420d4f0575f6b6e92c1acb5/phonenumbers-9.0.0.tar.gz", hash = "sha256:094a6f728e3c2b1906df4494a480743a3c797320f721f2b53f1400fd4d8ed5f5", size = 2296775 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/b4/d26590c0cb2f039e7b96a6c2e85210d86c286fc42a3c2e093c7cb2f3b2c8/phonenumbers-9.0.2.tar.gz", hash = "sha256:f590ee2b729bdd9873ca2d52989466add14c9953b48805c0aeb408348d4d6224", size = 2296774 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/82/943ce12a9db8932be3a1baff7cbc93524262942f2c41678242f410c8f420/phonenumbers-9.0.0-py2.py3-none-any.whl", hash = "sha256:f566eddf6219d9af9b4aad454ba411a1df565d13b875a490fd33d1d202c1148d", size = 2582416 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/d8/159ca512d76892f66c5a0d8fa60fe1989edff81b92e30a4cc18ee600ae06/phonenumbers-9.0.2-py2.py3-none-any.whl", hash = "sha256:dbcec6bdfdf3973f60b81dc0fcac3f7b1638f877ac42da4d7b46724ed413e2b9", size = 2582422 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1144,11 +1140,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.6"
|
||||
version = "4.3.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1162,7 +1158,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.1.0"
|
||||
version = "4.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cfgv" },
|
||||
@ -1171,9 +1167,9 @@ dependencies = [
|
||||
{ name = "pyyaml" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1205,15 +1201,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.5"
|
||||
version = "3.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/cf/dc1a4d45e3c6222fe272a245c5cea9a969a7157639da606ac7f2ab5de3a1/psycopg-3.2.5.tar.gz", hash = "sha256:f5f750611c67cb200e85b408882f29265c66d1de7f813add4f8125978bfd70e8", size = 156158 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/97/eea08f74f1c6dd2a02ee81b4ebfe5b558beb468ebbd11031adbf58d31be0/psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a", size = 156322 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/f3/14a1370b1449ca875d5e353ef02cb9db6b70bd46ec361c236176837c0be1/psycopg-3.2.5-py3-none-any.whl", hash = "sha256:b782130983e5b3de30b4c529623d3687033b4dafa05bb661fc6bf45837ca5879", size = 198749 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/7d/0ba52deff71f65df8ec8038adad86ba09368c945424a9bd8145d679a2c6a/psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58", size = 199077 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@ -1223,9 +1219,9 @@ c = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.2.5"
|
||||
version = "3.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/cb/468dcca82f08b47af59af4681ef39473cf5c0ef2e09775c701ccdf7284d6/psycopg_c-3.2.5.tar.gz", hash = "sha256:57ad4cfd28de278c424aaceb1f2ad5c7910466e315dfe84e403f3c7a0a2ce81b", size = 609318 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/f1/367a2429af2b97f6a46dc116206cd3b1cf668fca7ff3c22b979ea0686427/psycopg_c-3.2.6.tar.gz", hash = "sha256:b5fd4ce70f82766a122ca5076a36c4d5818eaa9df9bf76870bc83a064ffaed3a", size = 609304 }
|
||||
|
||||
[[package]]
|
||||
name = "ptyprocess"
|
||||
@ -1256,68 +1252,72 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
version = "2.11.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.2"
|
||||
version = "2.33.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-extra-types"
|
||||
version = "2.10.2"
|
||||
version = "2.10.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/ed/69f3f3de12c02ebd58b2f66ffb73d0f5a1b10b322227897499753cebe818/pydantic_extra_types-2.10.2.tar.gz", hash = "sha256:934d59ab7a02ff788759c3a97bc896f5cfdc91e62e4f88ea4669067a73f14b98", size = 86893 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/fa/6b268a47839f8af46ffeb5bb6aee7bded44fbad54e6bf826c11f17aef91a/pydantic_extra_types-2.10.3.tar.gz", hash = "sha256:dcc0a7b90ac9ef1b58876c9b8fdede17fbdde15420de9d571a9fccde2ae175bb", size = 95128 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/08/da/86bc9addde8a24348ac15f8f7dcb853f78e9573c7667800dd9bc60558678/pydantic_extra_types-2.10.2-py3-none-any.whl", hash = "sha256:9eccd55a2b7935cea25f0a67f6ff763d55d80c41d86b887d88915412ccf5b7fa", size = 35473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0a/f6f8e5f79d188e2f3fa9ecfccfa72538b685985dd5c7c2886c67af70e685/pydantic_extra_types-2.10.3-py3-none-any.whl", hash = "sha256:e8b372752b49019cd8249cc192c62a820d8019f5382a8789d0f887338a59c0f3", size = 37175 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1344,11 +1344,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.2.1"
|
||||
version = "3.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/1a/3544f4f299a47911c2ab3710f534e52fea62a633c96806995da5d25be4b2/pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a", size = 1067694 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/a7/c8a2d361bf89c0d9577c934ebb7421b25dc84bf3a8e3ac0a40aed9acc547/pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1", size = 107716 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1368,27 +1368,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "6.0.0"
|
||||
version = "6.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-django"
|
||||
version = "4.10.0"
|
||||
version = "4.11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a5/10/a096573b4b896f18a8390d9dafaffc054c1f613c60bf838300732e538890/pytest_django-4.10.0.tar.gz", hash = "sha256:1091b20ea1491fd04a310fc9aaff4c01b4e8450e3b157687625e16a6b5f3a366", size = 84710 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/4c/a4fe18205926216e1aebe1f125cba5bce444f91b6e4de4f49fa87e322775/pytest_django-4.10.0-py3-none-any.whl", hash = "sha256:57c74ef3aa9d89cae5a5d73fbb69a720a62673ade7ff13b9491872409a3f5918", size = 23975 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1405,11 +1405,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1520,40 +1520,40 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.9.10"
|
||||
version = "0.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/71/5759b2a6b2279bb77fe15b1435b89473631c2cd6374d45ccdb6b785810be/ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef", size = 3976488 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/db/6efda6381778eec7f35875b5cbefd194904832a1153d68d36d6b269d81a8/ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b", size = 10103150 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/f2/06cd9006077a8db61956768bc200a8e52515bf33a8f9b671ee527bb10d77/ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077", size = 10898637 },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/f5/af390a013c56022fe6f72b95c86eb7b2585c89cc25d63882d3bfe411ecf1/ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779", size = 10236012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/ca/b9bf954cfed165e1a0c24b86305d5c8ea75def256707f2448439ac5e0d8b/ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794", size = 10415338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/4d/2522dde4e790f1b59885283f8786ab0046958dfd39959c81acc75d347467/ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038", size = 9965277 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/7a/749f56f150eef71ce2f626a2f6988446c620af2f9ba2a7804295ca450397/ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f", size = 11541614 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/b2/7d9b8435222485b6aac627d9c29793ba89be40b5de11584ca604b829e960/ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82", size = 12198873 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/e0/a1a69ef5ffb5c5f9c31554b27e030a9c468fc6f57055886d27d316dfbabd/ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304", size = 11670190 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/61/c1c16df6e92975072c07f8b20dad35cd858e8462b8865bc856fe5d6ccb63/ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470", size = 13902301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/89/0af10c8af4363304fd8cb833bd407a2850c760b71edf742c18d5a87bb3ad/ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a", size = 11350132 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/e1/ecb4c687cbf15164dd00e38cf62cbab238cad05dd8b6b0fc68b0c2785e15/ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b", size = 10312937 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/4f/0e53fe5e500b65934500949361e3cd290c5ba60f0324ed59d15f46479c06/ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a", size = 9936683 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/a8/8183c4da6d35794ae7f76f96261ef5960853cd3f899c2671961f97a27d8e/ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159", size = 10950217 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/88/9b85a5a8af21e46a0639b107fcf9bfc31da4f1d263f2fc7fbe7199b47f0a/ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783", size = 11404521 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/52/047f35d3b20fd1ae9ccfe28791ef0f3ca0ef0b3e6c1a58badd97d450131b/ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe", size = 10320697 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/fe/00c78010e3332a6e92762424cf4c1919065707e962232797d0b57fd8267e/ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800", size = 11378665 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/7c/c83fe5cbb70ff017612ff36654edfebec4b1ef79b558b8e5fd933bab836b/ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e", size = 10460287 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.22.0"
|
||||
version = "2.25.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/b6/662988ecd2345bf6c3a5c306a9a3590852742eff91d0a78a143398b816f3/sentry_sdk-2.22.0.tar.gz", hash = "sha256:b4bf43bb38f547c84b2eadcefbe389b36ef75f3f38253d7a74d6b928c07ae944", size = 303539 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/85/2f/a0f732270cc7c1834f5ec45539aec87c360d5483a8bd788217a9102ccfbd/sentry_sdk-2.25.1.tar.gz", hash = "sha256:f9041b7054a7cf12d41eadabe6458ce7c6d6eea7a97cfe1b760b6692e9562cf0", size = 322190 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/7f/0e4459173e9671ba5f75a48dda2442bcc48a12c79e54e5789381c8c6a9bc/sentry_sdk-2.22.0-py2.py3-none-any.whl", hash = "sha256:3d791d631a6c97aad4da7074081a57073126c69487560c6f8bffcf586461de66", size = 325815 },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/b6/84049ab0967affbc7cc7590d86ae0170c1b494edb69df8786707100420e5/sentry_sdk-2.25.1-py2.py3-none-any.whl", hash = "sha256:60b016d0772789454dc55a284a6a44212044d4a16d9f8448725effee97aaf7f6", size = 339851 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1627,31 +1627,31 @@ tests = [
|
||||
requires-dist = [
|
||||
{ name = "cryptography", specifier = ">=44.0.2,<45.0.0" },
|
||||
{ name = "dict2xml", specifier = ">=1.7.6,<2.0.0" },
|
||||
{ name = "django", specifier = ">=4.2.20,<5.0.0" },
|
||||
{ name = "django", specifier = ">=5.2.0,<6.0.0" },
|
||||
{ name = "django-countries", specifier = ">=7.6.1,<8.0.0" },
|
||||
{ name = "django-haystack", specifier = ">=3.3.0,<4.0.0" },
|
||||
{ name = "django-honeypot", specifier = ">=1.2.1,<2.0.0" },
|
||||
{ name = "django-honeypot", git = "https://github.com/jamesturk/django-honeypot.git?rev=3986228" },
|
||||
{ name = "django-jinja", specifier = ">=2.11.0,<3.0.0" },
|
||||
{ name = "django-ninja", specifier = ">=1.3.0,<2.0.0" },
|
||||
{ name = "django-ninja-extra", specifier = ">=0.22.4,<1.0.0" },
|
||||
{ name = "django-ninja", specifier = ">=1.4.0,<2.0.0" },
|
||||
{ name = "django-ninja-extra", specifier = ">=0.22.9,<1.0.0" },
|
||||
{ name = "django-ordered-model", specifier = ">=3.7.4,<4.0.0" },
|
||||
{ name = "django-phonenumber-field", specifier = ">=8.0.0,<9.0.0" },
|
||||
{ name = "django-phonenumber-field", specifier = ">=8.1.0,<9.0.0" },
|
||||
{ name = "django-simple-captcha", specifier = ">=0.6.2,<1.0.0" },
|
||||
{ name = "environs", extras = ["django"], specifier = ">=14.1.0,<15.0.0" },
|
||||
{ name = "environs", extras = ["django"], specifier = ">=14.1.1,<15.0.0" },
|
||||
{ name = "honcho", specifier = ">=2.0.0" },
|
||||
{ name = "ical", specifier = ">=9.1.0,<10.0.0" },
|
||||
{ name = "jinja2", specifier = ">=3.1.6,<4.0.0" },
|
||||
{ name = "libsass", specifier = ">=0.23.0,<1.0.0" },
|
||||
{ name = "mistune", specifier = ">=3.1.2,<4.0.0" },
|
||||
{ name = "phonenumbers", specifier = ">=9.0.0,<10.0.0" },
|
||||
{ name = "mistune", specifier = ">=3.1.3,<4.0.0" },
|
||||
{ name = "phonenumbers", specifier = ">=9.0.2,<10.0.0" },
|
||||
{ name = "pillow", specifier = ">=11.1.0,<12.0.0" },
|
||||
{ name = "psutil", specifier = ">=7.0.0" },
|
||||
{ name = "pydantic-extra-types", specifier = ">=2.10.2,<3.0.0" },
|
||||
{ name = "pydantic-extra-types", specifier = ">=2.10.3,<3.0.0" },
|
||||
{ name = "python-dateutil", specifier = ">=2.9.0.post0,<3.0.0.0" },
|
||||
{ name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<6.0.0" },
|
||||
{ name = "redis", extras = ["hiredis"], specifier = ">=5.2.1,<6.0.0" },
|
||||
{ name = "reportlab", specifier = ">=4.3.1,<5.0.0" },
|
||||
{ name = "requests", specifier = ">=2.32.3" },
|
||||
{ name = "sentry-sdk", specifier = ">=2.22.0,<3.0.0" },
|
||||
{ name = "sentry-sdk", specifier = ">=2.25.1,<3.0.0" },
|
||||
{ name = "sphinx", specifier = ">=5,<6" },
|
||||
{ name = "tomli", specifier = ">=2.2.1,<3.0.0" },
|
||||
{ name = "xapian-haystack", specifier = ">=3.1.0,<4.0.0" },
|
||||
@ -1665,7 +1665,7 @@ dev = [
|
||||
{ name = "ipython", specifier = ">=9.0.2,<10.0.0" },
|
||||
{ name = "pre-commit", specifier = ">=4.1.0,<5.0.0" },
|
||||
{ name = "rjsmin", specifier = ">=1.2.4,<2.0.0" },
|
||||
{ name = "ruff", specifier = ">=0.9.10,<1.0.0" },
|
||||
{ name = "ruff", specifier = ">=0.11.1,<1.0.0" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "mkdocs", specifier = ">=1.6.1,<2.0.0" },
|
||||
@ -1674,7 +1674,7 @@ docs = [
|
||||
{ name = "mkdocstrings", specifier = ">=0.28.3,<1.0.0" },
|
||||
{ name = "mkdocstrings-python", specifier = ">=1.16.3,<2.0.0" },
|
||||
]
|
||||
prod = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" }]
|
||||
prod = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.6,<4.0.0" }]
|
||||
tests = [
|
||||
{ name = "beautifulsoup4", specifier = ">=4.13.3,<5" },
|
||||
{ name = "freezegun", specifier = ">=1.5.1,<2.0.0" },
|
||||
@ -1856,43 +1856,55 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
version = "4.13.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.1"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.29.3"
|
||||
version = "20.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/9c/57d19fa093bcf5ac61a48087dd44d00655f85421d1aa9722f8befbf3f40a/virtualenv-20.29.3.tar.gz", hash = "sha256:95e39403fcf3940ac45bc717597dba16110b74506131845d9b687d5e73d947ac", size = 4320280 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/eb/c6db6e3001d58c6a9e67c74bb7b4206767caa3ccc28c6b9eaf4c23fb4e34/virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170", size = 4301458 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
Loading…
x
Reference in New Issue
Block a user