Merge pull request #1044 from ae-utbm/fix-deprecations

Fix some deprecations
This commit is contained in:
thomas girod 2025-03-14 12:37:11 +01:00 committed by GitHub
commit bb3dfb7e8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 177 additions and 71 deletions

View File

@ -32,7 +32,7 @@ class TestRefoundAccount(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.skia = User.objects.get(username="skia") cls.skia = User.objects.get(username="skia")
# reffil skia's account # refill skia's account
cls.skia.customer.amount = 800 cls.skia.customer.amount = 800
cls.skia.customer.save() cls.skia.customer.save()
cls.refound_account_url = reverse("accounting:refound_account") cls.refound_account_url = reverse("accounting:refound_account")

View File

@ -17,7 +17,7 @@ import collections
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.db import transaction from django.db import transaction
from django.db.models import Sum from django.db.models import Sum
@ -846,27 +846,16 @@ class CloseCustomerAccountForm(forms.Form):
) )
class RefoundAccountView(FormView): class RefoundAccountView(UserPassesTestMixin, FormView):
"""Create a selling with the same amount than the current user money.""" """Create a selling with the same amount than the current user money."""
template_name = "accounting/refound_account.jinja" template_name = "accounting/refound_account.jinja"
form_class = CloseCustomerAccountForm form_class = CloseCustomerAccountForm
def permission(self, user): def test_func(self):
if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return self.request.user.is_root or self.request.user.is_in_group(
return True pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
else: )
raise PermissionDenied
def dispatch(self, request, *arg, **kwargs):
res = super().dispatch(request, *arg, **kwargs)
if self.permission(request.user):
return res
def post(self, request, *arg, **kwargs):
self.operator = request.user
if self.permission(request.user):
return super().post(self, request, *arg, **kwargs)
def form_valid(self, form): def form_valid(self, form):
self.customer = form.cleaned_data["user"] self.customer = form.cleaned_data["user"]
@ -887,7 +876,7 @@ class RefoundAccountView(FormView):
label=_("Refound account"), label=_("Refound account"),
unit_price=uprice, unit_price=uprice,
quantity=1, quantity=1,
seller=self.operator, seller=self.request.user,
customer=self.customer.customer, customer=self.customer.customer,
club=refound_club, club=refound_club,
counter=refound_club_counter, counter=refound_club_counter,

View File

@ -2,7 +2,7 @@ from datetime import datetime
from ninja import FilterSchema, ModelSchema from ninja import FilterSchema, ModelSchema
from ninja_extra import service_resolver from ninja_extra import service_resolver
from ninja_extra.controllers import RouteContext from ninja_extra.context import RouteContext
from pydantic import Field from pydantic import Field
from club.schemas import ClubProfileSchema from club.schemas import ClubProfileSchema

View File

@ -169,10 +169,9 @@ class CanCreateMixin(View):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
res = super().dispatch(request, *arg, **kwargs)
if not request.user.is_authenticated: if not request.user.is_authenticated:
raise PermissionDenied raise PermissionDenied
return res return super().dispatch(request, *arg, **kwargs)
def form_valid(self, form): def form_valid(self, form):
obj = form.instance obj = form.instance

View File

@ -919,6 +919,7 @@ Welcome to the wiki page!
"view_album", "view_album",
"view_peoplepicturerelation", "view_peoplepicturerelation",
"add_peoplepicturerelation", "add_peoplepicturerelation",
"add_page",
] ]
) )
) )

View File

@ -30,7 +30,7 @@ export const paginated = async <T>(
endpoint: PaginatedEndpoint<T>, endpoint: PaginatedEndpoint<T>,
options?: PaginatedRequest, options?: PaginatedRequest,
): Promise<T[]> => { ): Promise<T[]> => {
const maxPerPage = 199; const maxPerPage = 200;
const queryParams = options ?? ({} as PaginatedRequest); const queryParams = options ?? ({} as PaginatedRequest);
queryParams.query = queryParams.query ?? {}; queryParams.query = queryParams.query ?? {};
queryParams.query.page_size = maxPerPage; queryParams.query.page_size = maxPerPage;

View File

@ -251,21 +251,31 @@ $hovered-red-text-color: #ff4d4d;
justify-content: flex-start; justify-content: flex-start;
} }
>a { a, button {
font-size: 100%;
margin: 0;
text-align: right; text-align: right;
color: $text-color; color: $text-color;
&:hover { &:hover {
color: $hovered-text-color; color: $hovered-text-color;
} }
}
&:last-child { form#logout-form {
color: $red-text-color; margin: 0;
display: inline;
}
#logout-form button {
color: $red-text-color;
&:hover { &:hover {
color: $hovered-red-text-color; color: $hovered-red-text-color;
}
} }
background: none;
border: none;
cursor: pointer;
padding: 0;
} }
} }
} }

View File

@ -84,18 +84,18 @@
</ul> </ul>
<div id="content"> <div id="content">
{% block tabs %} {%- block tabs -%}
{% include "core/base/tabs.jinja" %} {% include "core/base/tabs.jinja" %}
{% endblock %} {%- endblock -%}
{% block errors%} {%- block errors -%}
{% if error %} {% if error %}
{{ error }} {{ error }}
{% endif %} {% endif %}
{% endblock %} {%- endblock -%}
{% block content %} {%- block content -%}
{% endblock %} {%- endblock -%}
</div> </div>
</div> </div>

View File

@ -59,7 +59,10 @@
</div> </div>
<div class="links"> <div class="links">
<a href="{{ url('core:user_tools') }}">{% trans %}Tools{% endtrans %}</a> <a href="{{ url('core:user_tools') }}">{% trans %}Tools{% endtrans %}</a>
<a href="{{ url('core:logout') }}">{% trans %}Logout{% endtrans %}</a> <form id="logout-form" method="post" action="{{ url("core:logout") }}">
{% csrf_token %}
<button type="submit">{% trans %}Logout{% endtrans %}</button>
</form>
</div> </div>
</div> </div>
<a <a

View File

@ -12,16 +12,15 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% macro print_page_name(page) %} {%- macro print_page_name(page) -%}
{% if page %} {%- if page -%}
{{ print_page_name(page.parent) }} > {{ print_page_name(page.parent) }} >
<a href="{{ url('core:page', page_name=page.get_full_name()) }}">{{ page.get_display_name() }}</a> <a href="{{ url('core:page', page_name=page.get_full_name()) }}">{{ page.get_display_name() }}</a>
{% endif %} {%- endif -%}
{% endmacro %} {%- endmacro -%}
{% block content %} {% block content %}
{{ print_page_name(page) }} {{ print_page_name(page) }}
<div class="tool_bar"> <div class="tool_bar">
<div class="tools"> <div class="tools">
{% if page %} {% if page %}

View File

@ -18,7 +18,9 @@ from smtplib import SMTPException
import freezegun import freezegun
import pytest import pytest
from bs4 import BeautifulSoup
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import Permission
from django.core import mail from django.core import mail
from django.core.cache import cache from django.core.cache import cache
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
@ -223,17 +225,19 @@ def test_full_markdown_syntax():
class TestPageHandling(TestCase): class TestPageHandling(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.root = User.objects.get(username="root") cls.group = baker.make(
cls.root_group = Group.objects.get(name="Root") Group, permissions=[Permission.objects.get(codename="add_page")]
)
cls.user = baker.make(User, groups=[cls.group])
def setUp(self): def setUp(self):
self.client.force_login(self.root) self.client.force_login(self.user)
def test_create_page_ok(self): def test_create_page_ok(self):
"""Should create a page correctly.""" """Should create a page correctly."""
response = self.client.post( response = self.client.post(
reverse("core:page_new"), reverse("core:page_new"),
{"parent": "", "name": "guy", "owner_group": self.root_group.id}, {"parent": "", "name": "guy", "owner_group": self.group.id},
) )
self.assertRedirects( self.assertRedirects(
response, reverse("core:page", kwargs={"page_name": "guy"}) response, reverse("core:page", kwargs={"page_name": "guy"})
@ -249,32 +253,38 @@ class TestPageHandling(TestCase):
def test_create_child_page_ok(self): def test_create_child_page_ok(self):
"""Should create a page correctly.""" """Should create a page correctly."""
# remove all other pages to make sure there is no side effect parent = baker.prepare(Page)
Page.objects.all().delete() parent.save(force_lock=True)
self.client.post( response = self.client.get(
reverse("core:page_new"), reverse("core:page_new") + f"?page={parent._full_name}/new"
{"parent": "", "name": "guy", "owner_group": str(self.root_group.id)},
) )
page = Page.objects.first()
self.client.post( assert response.status_code == 200
# The name and parent inputs should be already filled
soup = BeautifulSoup(response.content.decode(), "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)
response = self.client.post(
reverse("core:page_new"), reverse("core:page_new"),
{ {
"parent": str(page.id), "parent": str(parent.id),
"name": "bibou", "name": "new",
"owner_group": str(self.root_group.id), "owner_group": str(self.group.id),
}, },
) )
response = self.client.get( new_url = reverse("core:page", kwargs={"page_name": f"{parent._full_name}/new"})
reverse("core:page", kwargs={"page_name": "guy/bibou"}) assertRedirects(response, new_url, fetch_redirect_response=False)
) response = self.client.get(new_url)
assert response.status_code == 200 assert response.status_code == 200
assert '<a href="/page/guy/bibou/">' in str(response.content) assert f'<a href="/page/{parent._full_name}/new/">' in response.content.decode()
def test_access_child_page_ok(self): def test_access_child_page_ok(self):
"""Should display a page correctly.""" """Should display a page correctly."""
parent = Page(name="guy", owner_group=self.root_group) parent = Page(name="guy", owner_group=self.group)
parent.save(force_lock=True) parent.save(force_lock=True)
page = Page(name="bibou", owner_group=self.root_group, parent=parent) page = Page(name="bibou", owner_group=self.group, parent=parent)
page.save(force_lock=True) page.save(force_lock=True)
response = self.client.get( response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"}) reverse("core:page", kwargs={"page_name": "guy/bibou"})
@ -293,7 +303,8 @@ class TestPageHandling(TestCase):
def test_create_page_markdown_safe(self): def test_create_page_markdown_safe(self):
"""Should format the markdown and escape html correctly.""" """Should format the markdown and escape html correctly."""
self.client.post( self.client.post(
reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"} reverse("core:page_new"),
{"parent": "", "name": "guy", "owner_group": self.group.id},
) )
self.client.post( self.client.post(
reverse("core:page_edit", kwargs={"page_name": "guy"}), reverse("core:page_edit", kwargs={"page_name": "guy"}),

View File

@ -2,6 +2,7 @@ from datetime import timedelta
import pytest import pytest
from django.conf import settings from django.conf import settings
from django.contrib import auth
from django.core.management import call_command from django.core.management import call_command
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
@ -219,3 +220,12 @@ def test_user_update_groups(client: Client):
manageable_groups[1], manageable_groups[1],
*hidden_groups[:3], *hidden_groups[:3],
} }
@pytest.mark.django_db
def test_logout(client: Client):
user = baker.make(User)
client.force_login(user)
res = client.post(reverse("core:logout"))
assertRedirects(res, reverse("core:login"))
assert auth.get_user(client).is_anonymous

View File

@ -403,6 +403,7 @@ class FileModerationView(AllowFragment, ListView):
model = SithFile model = SithFile
template_name = "core/file_moderation.jinja" template_name = "core/file_moderation.jinja"
queryset = SithFile.objects.filter(is_moderated=False, is_in_sas=False) queryset = SithFile.objects.filter(is_moderated=False, is_in_sas=False)
ordering = "id"
paginate_by = 100 paginate_by = 100
def dispatch(self, request: HttpRequest, *args, **kwargs): def dispatch(self, request: HttpRequest, *args, **kwargs):

View File

@ -12,6 +12,7 @@
# OR WITHIN THE LOCAL FILE "LICENSE" # OR WITHIN THE LOCAL FILE "LICENSE"
# #
# #
from django.contrib.auth.mixins import PermissionRequiredMixin
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
@ -22,7 +23,6 @@ from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.auth.mixins import ( from core.auth.mixins import (
CanCreateMixin,
CanEditMixin, CanEditMixin,
CanEditPropMixin, CanEditPropMixin,
CanViewMixin, CanViewMixin,
@ -115,20 +115,22 @@ class PageRevView(CanViewMixin, DetailView):
return context return context
class PageCreateView(CanCreateMixin, CreateView): class PageCreateView(PermissionRequiredMixin, CreateView):
model = Page model = Page
form_class = PageForm form_class = PageForm
template_name = "core/page_prop.jinja" template_name = "core/page_prop.jinja"
permission_required = "core.add_page"
def get_initial(self): def get_initial(self):
init = {} init = super().get_initial()
if "page" in self.request.GET: if "page" not in self.request.GET:
page_name = self.request.GET["page"] return init
parent_name = "/".join(page_name.split("/")[:-1]) page_name = self.request.GET["page"].rsplit("/", maxsplit=1)
parent = Page.get_page_by_full_name(parent_name) if len(page_name) == 2:
parent = Page.get_page_by_full_name(page_name[0])
if parent is not None: if parent is not None:
init["parent"] = parent.id init["parent"] = parent.id
init["name"] = page_name.split("/")[-1] init["name"] = page_name[-1]
return init return init
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):

View File

@ -74,6 +74,8 @@ tests = [
"pytest-cov<7.0.0,>=6.0.0", "pytest-cov<7.0.0,>=6.0.0",
"pytest-django<5.0.0,>=4.10.0", "pytest-django<5.0.0,>=4.10.0",
"model-bakery<2.0.0,>=1.20.4", "model-bakery<2.0.0,>=1.20.4",
"beautifulsoup4>=4.13.3,<5",
"lxml>=5.3.1,<6",
] ]
docs = [ docs = [
"mkdocs<2.0.0,>=1.6.1", "mkdocs<2.0.0,>=1.6.1",

View File

@ -94,6 +94,13 @@ class SubscriptionNewUserForm(SubscriptionForm):
return email return email
def clean(self) -> dict[str, Any]: def clean(self) -> dict[str, Any]:
"""Initialize the [User][core.models.User] linked to this subscription.
Warning:
The `User` is initialized, but not saved.
Don't use it for operations that expect
a persisted object.
"""
member = User( member = User(
first_name=self.cleaned_data.get("first_name"), first_name=self.cleaned_data.get("first_name"),
last_name=self.cleaned_data.get("last_name"), last_name=self.cleaned_data.get("last_name"),

View File

@ -92,9 +92,13 @@ class Subscription(models.Model):
self.member.make_home() self.member.make_home()
def get_absolute_url(self): def get_absolute_url(self):
return reverse("core:user_edit", kwargs={"user_id": self.member.pk}) return reverse("core:user_edit", kwargs={"user_id": self.member_id})
def clean(self): def clean(self):
if self.member._state.adding:
# if the user is being created, then it makes no sense
# to check if the user is already subscribed
return
today = localdate() today = localdate()
threshold = timedelta(weeks=settings.SITH_SUBSCRIPTION_END) threshold = timedelta(weeks=settings.SITH_SUBSCRIPTION_END)
# a user may subscribe if : # a user may subscribe if :

68
uv.lock generated
View File

@ -60,6 +60,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336 }, { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336 },
] ]
[[package]]
name = "beautifulsoup4"
version = "4.13.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "soupsieve" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 },
]
[[package]] [[package]]
name = "bracex" name = "bracex"
version = "2.5.post1" version = "2.5.post1"
@ -755,6 +768,48 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/20/caf3c7cf2432d85263119798c45221ddf67bdd7dae8f626d14ff8db04040/libsass-0.23.0-cp38-abi3-win_amd64.whl", hash = "sha256:a2ec85d819f353cbe807432d7275d653710d12b08ec7ef61c124a580a8352f3c", size = 872914 }, { url = "https://files.pythonhosted.org/packages/ef/20/caf3c7cf2432d85263119798c45221ddf67bdd7dae8f626d14ff8db04040/libsass-0.23.0-cp38-abi3-win_amd64.whl", hash = "sha256:a2ec85d819f353cbe807432d7275d653710d12b08ec7ef61c124a580a8352f3c", size = 872914 },
] ]
[[package]]
name = "lxml"
version = "5.3.1"
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 }
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 },
]
[[package]] [[package]]
name = "markdown" name = "markdown"
version = "3.7" version = "3.7"
@ -1560,7 +1615,9 @@ prod = [
{ name = "psycopg", extra = ["c"] }, { name = "psycopg", extra = ["c"] },
] ]
tests = [ tests = [
{ name = "beautifulsoup4" },
{ name = "freezegun" }, { name = "freezegun" },
{ name = "lxml" },
{ name = "model-bakery" }, { name = "model-bakery" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-cov" }, { name = "pytest-cov" },
@ -1620,7 +1677,9 @@ docs = [
] ]
prod = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" }] prod = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" }]
tests = [ tests = [
{ name = "beautifulsoup4", specifier = ">=4.13.3,<5" },
{ name = "freezegun", specifier = ">=1.5.1,<2.0.0" }, { name = "freezegun", specifier = ">=1.5.1,<2.0.0" },
{ name = "lxml", specifier = ">=5.3.1,<6" },
{ name = "model-bakery", specifier = ">=1.20.4,<2.0.0" }, { name = "model-bakery", specifier = ">=1.20.4,<2.0.0" },
{ name = "pytest", specifier = ">=8.3.5,<9.0.0" }, { name = "pytest", specifier = ">=8.3.5,<9.0.0" },
{ name = "pytest-cov", specifier = ">=6.0.0,<7.0.0" }, { name = "pytest-cov", specifier = ">=6.0.0,<7.0.0" },
@ -1645,6 +1704,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 },
] ]
[[package]]
name = "soupsieve"
version = "2.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
]
[[package]] [[package]]
name = "sphinx" name = "sphinx"
version = "5.3.0" version = "5.3.0"