Graph de famille en frontend (#820)

* Remove graphviz and use cytoscape.js instead

* Frontend generated graphs
* Make installation easier and faster
* Better user experience
* Family api and improved interface
* Fix url history when using 0, improve button selection and reset reverse with reset button
* Use klay layout
* Add js translations and apply review comments
This commit is contained in:
Bartuccio Antoine 2024-09-17 12:10:06 +02:00 committed by GitHub
parent bf96d8a10c
commit f624b7c66d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1332 additions and 684 deletions

View File

@ -6,13 +6,13 @@ runs:
- name: Install apt packages - name: Install apt packages
uses: awalsh128/cache-apt-pkgs-action@latest uses: awalsh128/cache-apt-pkgs-action@latest
with: with:
packages: gettext libgraphviz-dev packages: gettext
version: 1.0 # increment to reset cache version: 1.0 # increment to reset cache
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt update sudo apt update
sudo apt install gettext libgraphviz-dev sudo apt install gettext
shell: bash shell: bash
- name: Set up python - name: Set up python

View File

@ -1,10 +1,19 @@
from typing import Annotated
import annotated_types
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from ninja_extra import ControllerBase, api_controller, route from ninja_extra import ControllerBase, api_controller, route
from ninja_extra.exceptions import PermissionDenied from ninja_extra.exceptions import PermissionDenied
from club.models import Mailing from club.models import Mailing
from core.schemas import MarkdownSchema from core.api_permissions import CanView
from core.models import User
from core.schemas import (
FamilyGodfatherSchema,
MarkdownSchema,
UserFamilySchema,
)
from core.templatetags.renderer import markdown from core.templatetags.renderer import markdown
@ -27,3 +36,45 @@ class MailingListController(ControllerBase):
).prefetch_related("subscriptions") ).prefetch_related("subscriptions")
data = "\n".join(m.fetch_format() for m in mailings) data = "\n".join(m.fetch_format() for m in mailings)
return data return data
DepthValue = Annotated[int, annotated_types.Ge(0), annotated_types.Le(10)]
DEFAULT_DEPTH = 4
@api_controller("/family")
class FamilyController(ControllerBase):
@route.get(
"/{user_id}",
permissions=[CanView],
response=UserFamilySchema,
url_name="family_graph",
)
def get_family_graph(
self,
user_id: int,
godfathers_depth: DepthValue = DEFAULT_DEPTH,
godchildren_depth: DepthValue = DEFAULT_DEPTH,
):
user: User = self.get_object_or_exception(User, pk=user_id)
relations = user.get_family(godfathers_depth, godchildren_depth)
if not relations:
# If the user has no relations, return only the user
# He is alone in its family, but the family exists nonetheless
return {"users": [user], "relationships": []}
user_ids = {r.from_user_id for r in relations} | {
r.to_user_id for r in relations
}
return {
"users": User.objects.filter(id__in=user_ids).distinct(),
"relationships": (
[
FamilyGodfatherSchema(
godchild=r.from_user_id, godfather=r.to_user_id
)
for r in relations
]
),
}

View File

@ -78,7 +78,7 @@ class CanView(BasePermission):
"""Check that this user has the permission to view the object of this route. """Check that this user has the permission to view the object of this route.
Wrap the `user.can_view(obj)` method. Wrap the `user.can_view(obj)` method.
To see an example, look at the exemple in the module docstring. To see an example, look at the example in the module docstring.
""" """
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool: def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
@ -94,7 +94,7 @@ class CanEdit(BasePermission):
"""Check that this user has the permission to edit the object of this route. """Check that this user has the permission to edit the object of this route.
Wrap the `user.can_edit(obj)` method. Wrap the `user.can_edit(obj)` method.
To see an example, look at the exemple in the module docstring. To see an example, look at the example in the module docstring.
""" """
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool: def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
@ -110,7 +110,7 @@ class IsOwner(BasePermission):
"""Check that this user owns the object of this route. """Check that this user owns the object of this route.
Wrap the `user.is_owner(obj)` method. Wrap the `user.is_owner(obj)` method.
To see an example, look at the exemple in the module docstring. To see an example, look at the example in the module docstring.
""" """
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool: def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:

View File

@ -57,6 +57,7 @@ from django.utils.functional import cached_property
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from pydantic.v1 import NonNegativeInt
if TYPE_CHECKING: if TYPE_CHECKING:
from club.models import Club from club.models import Club
@ -606,6 +607,41 @@ class User(AbstractBaseUser):
today.year - born.year - ((today.month, today.day) < (born.month, born.day)) today.year - born.year - ((today.month, today.day) < (born.month, born.day))
) )
def get_family(
self,
godfathers_depth: NonNegativeInt = 4,
godchildren_depth: NonNegativeInt = 4,
) -> set[User.godfathers.through]:
"""Get the family of the user, with the given depth.
Args:
godfathers_depth: The number of generations of godfathers to fetch
godchildren_depth: The number of generations of godchildren to fetch
Returns:
A list of family relationships in this user's family
"""
res = []
for depth, key, reverse_key in [
(godfathers_depth, "from_user_id", "to_user_id"),
(godchildren_depth, "to_user_id", "from_user_id"),
]:
if depth == 0:
continue
links = list(User.godfathers.through.objects.filter(**{key: self.id}))
res.extend(links)
for _ in range(1, depth):
ids = [getattr(c, reverse_key) for c in links]
links = list(
User.godfathers.through.objects.filter(
**{f"{key}__in": ids}
).exclude(id__in=[r.id for r in res])
)
if not links:
break
res.extend(links)
return set(res)
def email_user(self, subject, message, from_email=None, **kwargs): def email_user(self, subject, message, from_email=None, **kwargs):
"""Sends an email to this User.""" """Sends an email to this User."""
if from_email is None: if from_email is None:

View File

@ -1,3 +1,4 @@
from django.contrib.staticfiles.storage import staticfiles_storage
from ninja import ModelSchema, Schema from ninja import ModelSchema, Schema
from core.models import User from core.models import User
@ -13,3 +14,41 @@ class SimpleUserSchema(ModelSchema):
class MarkdownSchema(Schema): class MarkdownSchema(Schema):
text: str text: str
class UserProfileSchema(ModelSchema):
"""The necessary information to show a user profile"""
class Meta:
model = User
fields = ["id", "nick_name", "first_name", "last_name"]
display_name: str
profile_url: str
profile_pict: str
@staticmethod
def resolve_display_name(obj: User) -> str:
return obj.get_display_name()
@staticmethod
def resolve_profile_url(obj: User) -> str:
return obj.get_absolute_url()
@staticmethod
def resolve_profile_pict(obj: User) -> str:
if obj.profile_pict_id is None:
return staticfiles_storage.url("core/img/unknown.jpg")
return obj.profile_pict.get_download_url()
class FamilyGodfatherSchema(Schema):
godfather: int
godchild: int
class UserFamilySchema(Schema):
"""Represent a graph of a user's family"""
users: list[UserProfileSchema]
relationships: list[FamilyGodfatherSchema]

View File

@ -89,7 +89,7 @@ function update_query_string(key, value, action = History.REPLACE, url = null) {
if (!url){ if (!url){
url = new URL(window.location.href); url = new URL(window.location.href);
} }
if (!value) { if (value === undefined || value === null || value === "") {
// If the value is null, undefined or empty => delete it // If the value is null, undefined or empty => delete it
url.searchParams.delete(key) url.searchParams.delete(key)
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {

View File

@ -239,6 +239,7 @@ a:not(.button) {
padding: 9px 13px; padding: 9px 13px;
border: none; border: none;
text-decoration: none; text-decoration: none;
border-radius: 5px;
&.btn-blue { &.btn-blue {
background-color: $deepblue; background-color: $deepblue;

View File

@ -0,0 +1,267 @@
async function get_graph_data(url, godfathers_depth, godchildren_depth) {
let data = await (
await fetch(
`${url}?godfathers_depth=${godfathers_depth}&godchildren_depth=${godchildren_depth}`,
)
).json();
return [
...data.users.map((user) => {
return { data: user };
}),
...data.relationships.map((rel) => {
return {
data: { source: rel.godfather, target: rel.godchild },
};
}),
];
}
function create_graph(container, data, active_user_id) {
let cy = cytoscape({
boxSelectionEnabled: false,
autounselectify: true,
container: container,
elements: data,
minZoom: 0.5,
style: [
// the stylesheet for the graph
{
selector: "node",
style: {
label: "data(display_name)",
"background-image": "data(profile_pict)",
width: "100%",
height: "100%",
"background-fit": "cover",
"background-repeat": "no-repeat",
shape: "ellipse",
},
},
{
selector: "edge",
style: {
width: 5,
"line-color": "#ccc",
"target-arrow-color": "#ccc",
"target-arrow-shape": "triangle",
"curve-style": "bezier",
},
},
{
selector: ".traversed",
style: {
"border-width": "5px",
"border-style": "solid",
"border-color": "red",
"target-arrow-color": "red",
"line-color": "red",
},
},
{
selector: ".not-traversed",
style: {
"line-opacity": "0.5",
"background-opacity": "0.5",
"background-image-opacity": "0.5",
},
},
],
layout: {
name: "klay",
nodeDimensionsIncludeLabels: true,
fit: true,
klay: {
addUnnecessaryBendpoints: true,
direction: 'DOWN',
nodePlacement: 'INTERACTIVE',
layoutHierarchy: true
}
}
});
let active_user = cy
.getElementById(active_user_id)
.style("shape", "rectangle");
/* Reset graph */
let reset_graph = () => {
cy.elements((element) => {
if (element.hasClass("traversed")) {
element.removeClass("traversed");
}
if (element.hasClass("not-traversed")) {
element.removeClass("not-traversed");
}
});
};
let on_node_tap = (el) => {
reset_graph();
/* Create path on graph if selected isn't the targeted user */
if (el === active_user) {
return;
}
cy.elements((element) => {
element.addClass("not-traversed");
});
cy.elements()
.aStar({
root: el,
goal: active_user,
})
.path.forEach((el) => {
el.removeClass("not-traversed");
el.addClass("traversed");
});
};
cy.on("tap", "node", (tapped) => {
on_node_tap(tapped.target);
});
cy.zoomingEnabled(false);
/* Add context menu */
if (cy.cxtmenu === undefined) {
console.error(
"ctxmenu isn't loaded, context menu won't be available on graphs",
);
return cy;
}
cy.cxtmenu({
selector: "node",
commands: [
{
content: '<i class="fa fa-external-link fa-2x"></i>',
select: function (el) {
window.open(el.data().profile_url, "_blank").focus();
},
},
{
content: '<span class="fa fa-mouse-pointer fa-2x"></span>',
select: function (el) {
on_node_tap(el);
},
},
{
content: '<i class="fa fa-eraser fa-2x"></i>',
select: function (el) {
reset_graph();
},
},
],
});
return cy;
}
document.addEventListener("alpine:init", () => {
/*
This needs some constants to be set before the document has been loaded
api_url: base url for fetching the tree as a string
active_user: id of the user to fetch the tree from
depth_min: minimum tree depth for godfathers and godchildren as an int
depth_max: maximum tree depth for godfathers and godchildren as an int
*/
const default_depth = 2;
if (
typeof api_url === "undefined" ||
typeof active_user === "undefined" ||
typeof depth_min === "undefined" ||
typeof depth_max === "undefined"
) {
console.error("Some constants are not set before using the family_graph script, please look at the documentation");
return;
}
function get_initial_depth(prop) {
let value = parseInt(initialUrlParams.get(prop));
if (isNaN(value) || value < depth_min || value > depth_max) {
return default_depth;
}
return value;
}
Alpine.data("graph", () => ({
loading: false,
godfathers_depth: get_initial_depth("godfathers_depth"),
godchildren_depth: get_initial_depth("godchildren_depth"),
reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === 'true',
graph: undefined,
graph_data: {},
async init() {
let delayed_fetch = Alpine.debounce(async () => {
this.fetch_graph_data();
}, 100);
["godfathers_depth", "godchildren_depth"].forEach((param) => {
this.$watch(param, async (value) => {
if (value < depth_min || value > depth_max) {
return;
}
update_query_string(param, value, History.REPLACE);
delayed_fetch();
});
});
this.$watch("reverse", async (value) => {
update_query_string("reverse", value, History.REPLACE);
this.reverse_graph();
});
this.$watch("graph_data", async () => {
await this.generate_graph();
if (this.reverse) {
await this.reverse_graph();
}
});
this.fetch_graph_data();
},
async screenshot() {
const link = document.createElement("a");
link.href = this.graph.jpg();
link.download = gettext("family_tree.%(extension)s", "jpg");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
async reset() {
this.reverse = false;
this.godfathers_depth = default_depth;
this.godchildren_depth = default_depth;
},
async reverse_graph() {
this.graph.elements((el) => {
el.position(new Object({ x: -el.position().x, y: -el.position().y }));
});
this.graph.center(this.graph.elements());
},
async fetch_graph_data() {
this.graph_data = await get_graph_data(
api_url,
this.godfathers_depth,
this.godchildren_depth,
);
},
async generate_graph() {
this.loading = true;
this.graph = create_graph(
$(this.$refs.graph),
this.graph_data,
active_user,
);
this.loading = false;
},
}));
});

View File

@ -1,3 +1,85 @@
.graph {
width: 100%;
height: 70vh;
display: block;
}
.graph-toolbar {
margin-top: 10px;
margin-bottom: 10px;
display: flex;
flex-direction: row;
justify-content: space-around;
gap: 30px;
.toolbar-column{
display: flex;
flex-direction: column;
gap: 20px;
min-width: 30%;
}
.toolbar-input {
display: flex;
flex-direction: row;
gap: 20px;
align-items: center;
width: 100%;
label {
max-width: 70%;
text-align: left;
margin-bottom: 0;
}
.depth-choice {
white-space: nowrap;
input[type="number"] {
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
}
}
button {
background: none;
& > .fa {
border-radius: 50%;
font-size: 12px;
padding: 5px;
}
&:enabled > .fa {
background-color: #354a5f;
color: white;
}
&:enabled:hover > .fa {
color: white;
background-color: #35405f; // just a bit darker
}
&:disabled > .fa {
background-color: gray;
color: white;
}
}
}
input {
align-self: center;
max-width: 40px;
}
}
@media screen and (max-width: 500px) {
flex-direction: column;
gap: 20px;
.toolbar-column {
min-width: 100%;
}
}
}
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -9,6 +91,14 @@
margin: 0; margin: 0;
} }
} }
#family-tree-link {
display: inline-block;
margin-top: 10px;
text-align: center;
@media (min-width: 450px) {
margin-right: auto;
}
}
.users { .users {
display: flex; display: flex;
@ -90,7 +180,7 @@
} }
} }
&:last-of-type { &.delete {
margin-top: 10px; margin-top: 10px;
display: block; display: block;
text-align: center; text-align: center;
@ -98,7 +188,7 @@
@media (max-width: 375px) { @media (max-width: 375px) {
position: absolute; position: absolute;
bottom: 0%; bottom: 0;
right: 0; right: 0;
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -112,7 +112,7 @@
{% macro delete_godfather(user, profile, godfather, is_father) %} {% macro delete_godfather(user, profile, godfather, is_father) %}
{% if user == profile or user.is_root or user.is_board_member %} {% if user == profile or user.is_root or user.is_board_member %}
<a href="{{ url("core:user_godfathers_delete", user_id=profile.id, godfather_id=godfather.id, is_father=is_father) }}">{% trans %}Delete{% endtrans %}</a> <a class="delete" href="{{ url("core:user_godfathers_delete", user_id=profile.id, godfather_id=godfather.id, is_father=is_father) }}">{% trans %}Delete{% endtrans %}</a>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}

View File

@ -11,14 +11,20 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<a href="{{ url('core:user_godfathers_tree_pict', user_id=profile.id) }}?family"> {% if godchildren or godfathers %}
{% trans %}Show family picture{% endtrans %} <a
</a> href="{{ url('core:user_godfathers_tree', user_id=profile.id) }}"
class="btn btn-blue"
id="family-tree-link"
>
{% trans %}Show family tree{% endtrans %}
</a>
{% endif %}
<h4>{% trans %}Godfathers / Godmothers{% endtrans %}</h4> <h4>{% trans %}Godfathers / Godmothers{% endtrans %}</h4>
{% if profile.godfathers.exists() %} {% if godfathers %}
<ul class="users"> <ul class="users">
{% for u in profile.godfathers.all() %} {% for u in godfathers %}
<li class="users-card"> <li class="users-card">
<a href="{{ url('core:user_godfathers', user_id=u.id) }}" class="mini_profile_link"> <a href="{{ url('core:user_godfathers', user_id=u.id) }}" class="mini_profile_link">
{{ u.get_mini_item() | safe }} {{ u.get_mini_item() | safe }}
@ -28,17 +34,14 @@
{% endfor %} {% endfor %}
</ul> </ul>
<a href="{{ url('core:user_godfathers_tree', user_id=profile.id) }}">
{% trans %}Show ancestors tree{% endtrans %}
</a>
{% else %} {% else %}
<p>{% trans %}No godfathers / godmothers{% endtrans %} <p>{% trans %}No godfathers / godmothers{% endtrans %}
{% endif %} {% endif %}
<h4>{% trans %}Godchildren{% endtrans %}</h4> <h4>{% trans %}Godchildren{% endtrans %}</h4>
{% if profile.godchildren.exists() %} {% if godchildren %}
<ul class="users"> <ul class="users">
{% for u in profile.godchildren.all() %} {% for u in godchildren %}
<li class="users-card"> <li class="users-card">
<a href="{{ url('core:user_godfathers', user_id=u.id) }}" class="mini_profile_link"> <a href="{{ url('core:user_godfathers', user_id=u.id) }}" class="mini_profile_link">
{{ u.get_mini_item()|safe }} {{ u.get_mini_item()|safe }}
@ -47,10 +50,6 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<a href="{{ url('core:user_godfathers_tree', user_id=profile.id) }}?descent">
{% trans %}Show descent tree{% endtrans %}
</a>
{% else %} {% else %}
<p>{% trans %}No godchildren{% endtrans %} <p>{% trans %}No godchildren{% endtrans %}
{% endif %} {% endif %}

View File

@ -1,54 +1,105 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% set depth_min=0 %}
{% set depth_max=10 %}
{%- block additional_css -%}
<link rel="stylesheet" href="{{ scss('user/user_godfathers.scss') }}">
{%- endblock -%}
{% block additional_js %}
<script src="{{ static("vendored/cytoscape/cytoscape.min.js") }}" defer></script>
<script src="{{ static("vendored/cytoscape/cytoscape-cxtmenu.min.js") }}" defer></script>
<script src="{{ static("vendored/cytoscape/klay.min.js") }}" defer></script>
<script src="{{ static("vendored/cytoscape/cytoscape-klay.min.js") }}" defer></script>
<script src="{{ static("user/js/family_graph.js") }}" defer></script>
{% endblock %}
{% block title %} {% block title %}
{% if param == "godchildren" %} {% trans user_name=profile.get_display_name() %}{{ user_name }}'s family tree{% endtrans %}
{% trans user_name=profile.get_display_name() %}{{ user_name }}'s godchildren{% endtrans %}
{% else %}
{% trans user_name=profile.get_display_name() %}{{ user_name }}'s godfathers{% endtrans %}
{% endif %}
{% endblock %} {% endblock %}
{% macro display_members_list(user) %}
{% if user.__getattribute__(param).exists() %}
<ul>
{% for u in user.__getattribute__(param).all() %}
<li>
<a href="{{ url("core:user_godfathers", user_id=u.id) }}">
{{ u.get_short_name() }}
</a>
{% if u in members_set %}
{% trans %}Already seen (check above){% endtrans %}
{% else %}
{{ members_set.add(u) or "" }}
{{ display_members_list(u) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{% block content %} {% block content %}
<p><a href="{{ url("core:user_godfathers", user_id=profile.id) }}"> <div x-data="graph" :aria-busy="loading">
{% trans %}Back to family{% endtrans %}</a></p> <div class="graph-toolbar">
{% if profile.__getattribute__(param).exists() %} <div class="toolbar-column">
{% if param == "godchildren" %} <div class="toolbar-input">
<p><a href="{{ url("core:user_godfathers_tree_pict", user_id=profile.id) }}?descent"> <label for="godfather-depth-input">
{% trans %}Show a picture of the tree{% endtrans %}</a></p> {% trans min=depth_min, max=depth_max %}Max godfather depth between {{ min }} and {{ max }}{% endtrans %}
<h4>{% trans u=profile.get_short_name() %}Descent tree of {{ u }}{% endtrans %}</h4> </label>
{% else %} <span class="depth-choice">
<p><a href="{{ url("core:user_godfathers_tree_pict", user_id=profile.id) }}?ancestors"> <button
{% trans %}Show a picture of the tree{% endtrans %}</a></p> @click="godfathers_depth--"
<h4>{% trans u=profile.get_short_name() %}Ancestors tree of {{ u }}{% endtrans %}</h4> :disabled="godfathers_depth <= {{ depth_min }}"
{% endif %} ><i class="fa fa-minus fa-xs"></i></button>
{{ members_set.add(profile) or "" }} <input
{{ display_members_list(profile) }} x-model="godfathers_depth"
{% else %} x-ref="godfather_depth_input"
{% if param == "godchildren" %} type="number"
<p>{% trans %}No godchildren{% endtrans %} name="godfathers_depth"
{% else %} id="godfather-depth-input"
<p>{% trans %}No godfathers / godmothers{% endtrans %} min="{{ depth_min }}"
{% endif %} max="{{ depth_max }}"
{% endif %} />
<button
@click="godfathers_depth++"
:disabled="godfathers_depth >= {{ depth_max }}"
><i class="fa fa-plus"
></i></button>
</span>
</div>
<div class="toolbar-input">
<label for="godchild-depth-input">
{% trans min=depth_min, max=depth_max %}Max godchildren depth between {{ min }} and {{ max }}{% endtrans %}
</label>
<span class="depth-choice">
<button
@click="godchildren_depth--"
:disabled="godchildren_depth <= {{ depth_min }}"
><i
class="fa fa-minus fa-xs"
></i></button>
<input
x-model="godchildren_depth"
type="number"
name="godchildren_depth"
id="godchild-depth-input"
min="{{ depth_min }}"
max="{{ depth_max }}"
/>
<button
@click="godchildren_depth++"
:disabled="godchildren_depth >= {{ depth_max }}"
><i class="fa fa-plus"
></i></button>
</span>
</div>
</div>
<div class="toolbar-column">
<div class="toolbar-input">
<label for="reverse-checkbox">{% trans %}Reverse{% endtrans %}</label>
<input x-model="reverse" type="checkbox" name="reverse" id="reverse-checkbox">
</div>
<button class="btn btn-grey" @click="reset">
{% trans %}Reset{% endtrans %}
</button>
<button class="btn btn-grey" @click="screenshot">
<i class="fa fa-camera"></i>
{% trans %}Save{% endtrans %}
</button>
</div>
</div>
<div x-ref="graph" class="graph"></div>
</div>
<script>
const api_url = "{{ api_url }}";
const active_user = "{{ object.id }}"
const depth_min = {{ depth_min }};
const depth_max = {{ depth_max }};
</script>
{% endblock %} {% endblock %}

0
core/tests/__init__.py Normal file
View File

187
core/tests/test_family.py Normal file
View File

@ -0,0 +1,187 @@
from django.test import TestCase
from django.urls import reverse
from model_bakery import baker
from core.baker_recipes import subscriber_user
from core.models import User
class TestFetchFamilyApi(TestCase):
@classmethod
def setUpTestData(cls):
# Relations (A -> B means A is the godchild of B):
# main_user -> user0 -> user3
# -> user1 -> user6 -> user7 -> user8 -> user9
# -> user2 -> user10
#
# main_user <- user3 <- user11
# <- user12
# <- user4 <- user13
# <- user14
# <- user15 <- user16
# <- user5
cls.main_user = baker.make(User)
cls.users = baker.make(User, _quantity=17)
cls.main_user.godfathers.add(*cls.users[0:3])
cls.main_user.godchildren.add(*cls.users[3:6])
cls.users[1].godfathers.add(cls.users[6])
cls.users[6].godfathers.add(cls.users[7])
cls.users[7].godfathers.add(cls.users[8])
cls.users[8].godfathers.add(cls.users[9])
cls.users[2].godfathers.add(cls.users[10])
cls.users[3].godchildren.add(cls.users[11], cls.users[12])
cls.users[4].godchildren.add(*cls.users[13:16])
cls.users[15].godchildren.add(cls.users[16])
cls.root_user = baker.make(User, is_superuser=True)
cls.subscriber_user = subscriber_user.make()
def setUp(self):
self.maxDiff = None
def test_fetch_family_forbidden(self):
# Anonymous user
response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id])
)
assert response.status_code == 403
self.client.force_login(baker.make(User)) # unsubscribed user
response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id])
)
assert response.status_code == 403
def test_fetch_family_hidden_user(self):
self.main_user.is_subscriber_viewable = False
self.main_user.save()
for user_to_login, error_code in [
(self.main_user, 200),
(self.subscriber_user, 403),
(self.root_user, 200),
]:
self.client.force_login(user_to_login)
response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id])
)
assert response.status_code == error_code
def test_fetch_family_with_zero_depth(self):
"""Fetch the family with a depth of 0."""
self.client.force_login(self.main_user)
response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=0&godchildren_depth=0"
)
assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [self.main_user.id]
assert response.json()["relationships"] == []
def test_fetch_empty_family(self):
empty_user = baker.make(User)
self.client.force_login(empty_user)
response = self.client.get(reverse("api:family_graph", args=[empty_user.id]))
assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [empty_user.id]
assert response.json()["relationships"] == []
def test_fetch_whole_family(self):
self.client.force_login(self.main_user)
response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=10&godchildren_depth=10"
)
assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [
self.main_user.id,
*[u.id for u in self.users],
]
self.assertCountEqual(
response.json()["relationships"],
[
{"godfather": self.users[0].id, "godchild": self.main_user.id},
{"godfather": self.users[1].id, "godchild": self.main_user.id},
{"godfather": self.users[2].id, "godchild": self.main_user.id},
{"godfather": self.main_user.id, "godchild": self.users[3].id},
{"godfather": self.main_user.id, "godchild": self.users[4].id},
{"godfather": self.main_user.id, "godchild": self.users[5].id},
{"godfather": self.users[6].id, "godchild": self.users[1].id},
{"godfather": self.users[7].id, "godchild": self.users[6].id},
{"godfather": self.users[8].id, "godchild": self.users[7].id},
{"godfather": self.users[9].id, "godchild": self.users[8].id},
{"godfather": self.users[10].id, "godchild": self.users[2].id},
{"godfather": self.users[3].id, "godchild": self.users[11].id},
{"godfather": self.users[3].id, "godchild": self.users[12].id},
{"godfather": self.users[4].id, "godchild": self.users[13].id},
{"godfather": self.users[4].id, "godchild": self.users[14].id},
{"godfather": self.users[4].id, "godchild": self.users[15].id},
{"godfather": self.users[15].id, "godchild": self.users[16].id},
],
)
def test_fetch_family_first_level(self):
"""Fetch only the first level of the family."""
self.client.force_login(self.main_user)
response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=1&godchildren_depth=1"
)
assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [
self.main_user.id,
*[u.id for u in self.users[:6]],
]
self.assertCountEqual(
response.json()["relationships"],
[
{"godfather": self.users[0].id, "godchild": self.main_user.id},
{"godfather": self.users[1].id, "godchild": self.main_user.id},
{"godfather": self.users[2].id, "godchild": self.main_user.id},
{"godfather": self.main_user.id, "godchild": self.users[3].id},
{"godfather": self.main_user.id, "godchild": self.users[4].id},
{"godfather": self.main_user.id, "godchild": self.users[5].id},
],
)
def test_fetch_family_only_godfathers(self):
"""Fetch only the godfathers."""
self.client.force_login(self.main_user)
response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=10&godchildren_depth=0"
)
assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [
self.main_user.id,
*[u.id for u in self.users[:3]],
*[u.id for u in self.users[6:11]],
]
self.assertCountEqual(
response.json()["relationships"],
[
{"godfather": self.users[0].id, "godchild": self.main_user.id},
{"godfather": self.users[1].id, "godchild": self.main_user.id},
{"godfather": self.users[2].id, "godchild": self.main_user.id},
{"godfather": self.users[6].id, "godchild": self.users[1].id},
{"godfather": self.users[7].id, "godchild": self.users[6].id},
{"godfather": self.users[8].id, "godchild": self.users[7].id},
{"godfather": self.users[9].id, "godchild": self.users[8].id},
{"godfather": self.users[10].id, "godchild": self.users[2].id},
],
)
def test_nb_queries(self):
# The number of queries should be 1 per level of existing depth.
with self.assertNumQueries(0):
self.main_user.get_family(godfathers_depth=0, godchildren_depth=0)
with self.assertNumQueries(3):
self.main_user.get_family(godfathers_depth=3, godchildren_depth=0)
with self.assertNumQueries(3):
self.main_user.get_family(godfathers_depth=0, godchildren_depth=3)
with self.assertNumQueries(6):
self.main_user.get_family(godfathers_depth=3, godchildren_depth=3)
with self.assertNumQueries(4):
# If a level is empty, the next ones should not be queried.
self.main_user.get_family(godfathers_depth=0, godchildren_depth=10)

View File

@ -112,11 +112,6 @@ urlpatterns = [
UserGodfathersTreeView.as_view(), UserGodfathersTreeView.as_view(),
name="user_godfathers_tree", name="user_godfathers_tree",
), ),
path(
"user/<int:user_id>/godfathers/tree/pict/",
UserGodfathersTreePictureView.as_view(),
name="user_godfathers_tree_pict",
),
path( path(
"user/<int:user_id>/godfathers/<int:godfather_id>/<bool:is_father>/delete/", "user/<int:user_id>/godfathers/<int:godfather_id>/<bool:is_father>/delete/",
delete_user_godfather, delete_user_godfather,

View File

@ -335,9 +335,40 @@ class UserGodfathersForm(forms.Form):
label=_("Add"), label=_("Add"),
) )
user = AutoCompleteSelectField( user = AutoCompleteSelectField(
"users", required=True, label=_("Select user"), help_text=None "users", required=True, label=_("Select user"), help_text=""
) )
def __init__(self, *args, user: User, **kwargs):
super().__init__(*args, **kwargs)
self.target_user = user
def clean_user(self):
other_user = self.cleaned_data.get("user")
if not other_user:
raise ValidationError(_("This user does not exist"))
if other_user == self.target_user:
raise ValidationError(_("You cannot be related to yourself"))
return other_user
def clean(self):
super().clean()
if not self.is_valid():
return self.cleaned_data
other_user = self.cleaned_data["user"]
if self.cleaned_data["type"] == "godfather":
if self.target_user.godfathers.contains(other_user):
self.add_error(
"user",
_("%s is already your godfather") % (other_user.get_short_name()),
)
else:
if self.target_user.godchildren.contains(other_user):
self.add_error(
"user",
_("%s is already your godchild") % (other_user.get_short_name()),
)
return self.cleaned_data
class PagePropForm(forms.ModelForm): class PagePropForm(forms.ModelForm):
error_css_class = "error" error_css_class = "error"

View File

@ -34,7 +34,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import Http404, HttpResponse from django.http import Http404
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@ -323,7 +323,7 @@ def delete_user_godfather(request, user_id, godfather_id, is_father):
return redirect("core:user_godfathers", user_id=user_id) return redirect("core:user_godfathers", user_id=user_id)
class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView, FormView):
"""Display a user's godfathers.""" """Display a user's godfathers."""
model = User model = User
@ -331,27 +331,23 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
context_object_name = "profile" context_object_name = "profile"
template_name = "core/user_godfathers.jinja" template_name = "core/user_godfathers.jinja"
current_tab = "godfathers" current_tab = "godfathers"
form_class = UserGodfathersForm
def post(self, request, *args, **kwargs): def get_form_kwargs(self):
self.object = self.get_object() return super().get_form_kwargs() | {"user": self.object}
self.form = UserGodfathersForm(request.POST)
if self.form.is_valid() and self.form.cleaned_data["user"] != self.object: def form_valid(self, form):
if self.form.cleaned_data["type"] == "godfather": if form.cleaned_data["type"] == "godfather":
self.object.godfathers.add(self.form.cleaned_data["user"]) self.object.godfathers.add(form.cleaned_data["user"])
self.object.save() else:
else: self.object.godchildren.add(form.cleaned_data["user"])
self.object.godchildren.add(self.form.cleaned_data["user"]) return redirect("core:user_godfathers", user_id=self.object.id)
self.object.save()
self.form = UserGodfathersForm()
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) return super().get_context_data(**kwargs) | {
try: "godfathers": list(self.object.godfathers.select_related("profile_pict")),
kwargs["form"] = self.form "godchildren": list(self.object.godchildren.select_related("profile_pict")),
except: }
kwargs["form"] = UserGodfathersForm()
return kwargs
class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView): class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView):
@ -365,86 +361,12 @@ class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
if "descent" in self.request.GET: kwargs["api_url"] = reverse(
kwargs["param"] = "godchildren" "api:family_graph", kwargs={"user_id": self.object.id}
else: )
kwargs["param"] = "godfathers"
kwargs["members_set"] = set()
return kwargs return kwargs
class UserGodfathersTreePictureView(CanViewMixin, DetailView):
"""Display a user's tree as a picture."""
model = User
pk_url_kwarg = "user_id"
def build_complex_graph(self):
import pygraphviz as pgv
self.depth = int(self.request.GET.get("depth", 4))
if self.param == "godfathers":
self.graph = pgv.AGraph(strict=False, directed=True, rankdir="BT")
else:
self.graph = pgv.AGraph(strict=False, directed=True)
family = set()
self.level = 1
# Since the tree isn't very deep, we can build it recursively
def crawl_family(user):
if self.level > self.depth:
return
self.level += 1
for u in user.__getattribute__(self.param).all():
self.graph.add_edge(user.get_short_name(), u.get_short_name())
if u not in family:
family.add(u)
crawl_family(u)
self.level -= 1
self.graph.add_node(self.object.get_short_name())
family.add(self.object)
crawl_family(self.object)
def build_family_graph(self):
import pygraphviz as pgv
self.graph = pgv.AGraph(strict=False, directed=True)
self.graph.add_node(self.object.get_short_name())
for u in self.object.godfathers.all():
self.graph.add_edge(u.get_short_name(), self.object.get_short_name())
for u in self.object.godchildren.all():
self.graph.add_edge(self.object.get_short_name(), u.get_short_name())
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if "descent" in self.request.GET:
self.param = "godchildren"
elif "ancestors" in self.request.GET:
self.param = "godfathers"
else:
self.param = "family"
if self.param == "family":
self.build_family_graph()
else:
self.build_complex_graph()
# Pimp the graph before display
self.graph.node_attr["color"] = "lightblue"
self.graph.node_attr["style"] = "filled"
main_node = self.graph.get_node(self.object.get_short_name())
main_node.attr["color"] = "sandybrown"
main_node.attr["shape"] = "rect"
if self.param == "godchildren":
self.graph.graph_attr["label"] = _("Godchildren")
elif self.param == "godfathers":
self.graph.graph_attr["label"] = _("Family")
else:
self.graph.graph_attr["label"] = _("Family")
img = self.graph.draw(format="png", prog="dot")
return HttpResponse(img, content_type="image/png")
class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
"""Display a user's stats.""" """Display a user's stats."""

View File

@ -31,7 +31,8 @@ Il faut d'abord générer un fichier de traductions,
l'éditer et enfin le compiler au format binaire pour qu'il soit lu par le serveur. l'éditer et enfin le compiler au format binaire pour qu'il soit lu par le serveur.
```bash ```bash
./manage.py makemessages --locale=fr --ignore "env/*" -e py,jinja ./manage.py makemessages --locale=fr -e py,jinja # Pour le backend
./manage.py makemessages --locale=fr -d djangojs # Pour le frontend
``` ```
## Éditer le fichier django.po ## Éditer le fichier django.po

View File

@ -8,7 +8,6 @@ Certaines dépendances sont nécessaires niveau système :
- zlib1g-dev - zlib1g-dev
- python - python
- gettext - gettext
- graphviz
### Installer WSL ### Installer WSL
@ -71,8 +70,8 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
Puis installez les autres dépendances : Puis installez les autres dépendances :
```bash ```bash
sudo apt install build-essentials libssl-dev libjpeg-dev zlib1g-dev python-dev \ sudo apt install build-essential libssl-dev libjpeg-dev zlib1g-dev python-dev \
libffi-dev python-dev-is-python3 libgraphviz-dev pkg-config \ libffi-dev python-dev-is-python3 pkg-config \
gettext git pipx gettext git pipx
pipx install poetry pipx install poetry
@ -85,7 +84,7 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
sudo pacman -S python sudo pacman -S python
sudo pacman -S gcc git graphviz gettext graphviz pkgconf python-poetry sudo pacman -S gcc git gettext pkgconf python-poetry
``` ```
=== "macOS" === "macOS"
@ -94,11 +93,7 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
Il est également nécessaire d'avoir installé xcode Il est également nécessaire d'avoir installé xcode
```bash ```bash
echo 'export PATH="$(brew --prefix graphviz)/bin:$PATH"' >> ~/.zshrc brew install git python pipx
echo 'export CFLAGS="-isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I $(brew --prefix graphviz)/include"' >> ~/.zshrc
echo 'export LDFLAGS="-L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -L $(brew --prefix graphviz)/lib"' >> ~/.zshrc
brew install git python graphviz pipx
pipx install poetry pipx install poetry
# Pour bien configurer gettext # Pour bien configurer gettext

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-08 13:30+0200\n" "POT-Creation-Date: 2024-09-17 11:24+0200\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Skia <skia@libskia.so>\n" "Last-Translator: Skia <skia@libskia.so>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -41,7 +41,7 @@ msgstr "code postal"
msgid "country" msgid "country"
msgstr "pays" msgstr "pays"
#: accounting/models.py:55 core/models.py:364 #: accounting/models.py:55 core/models.py:365
msgid "phone" msgid "phone"
msgstr "téléphone" msgstr "téléphone"
@ -127,8 +127,8 @@ msgstr "numéro"
msgid "journal" msgid "journal"
msgstr "classeur" msgstr "classeur"
#: accounting/models.py:261 core/models.py:904 core/models.py:1431 #: accounting/models.py:261 core/models.py:940 core/models.py:1467
#: core/models.py:1476 core/models.py:1505 core/models.py:1529 #: core/models.py:1512 core/models.py:1541 core/models.py:1565
#: counter/models.py:585 counter/models.py:678 counter/models.py:888 #: counter/models.py:585 counter/models.py:678 counter/models.py:888
#: eboutic/models.py:57 eboutic/models.py:173 forum/models.py:311 #: eboutic/models.py:57 eboutic/models.py:173 forum/models.py:311
#: forum/models.py:412 stock/models.py:96 #: forum/models.py:412 stock/models.py:96
@ -166,7 +166,7 @@ msgid "accounting type"
msgstr "type comptable" msgstr "type comptable"
#: accounting/models.py:299 accounting/models.py:438 accounting/models.py:471 #: accounting/models.py:299 accounting/models.py:438 accounting/models.py:471
#: accounting/models.py:503 core/models.py:1504 core/models.py:1530 #: accounting/models.py:503 core/models.py:1540 core/models.py:1566
#: counter/models.py:644 #: counter/models.py:644
msgid "label" msgid "label"
msgstr "étiquette" msgstr "étiquette"
@ -219,7 +219,7 @@ msgstr "Compte"
msgid "Company" msgid "Company"
msgstr "Entreprise" msgstr "Entreprise"
#: accounting/models.py:312 core/models.py:311 sith/settings.py:413 #: accounting/models.py:312 core/models.py:312 sith/settings.py:413
#: stock/templates/stock/shopping_list_items.jinja:37 #: stock/templates/stock/shopping_list_items.jinja:37
msgid "Other" msgid "Other"
msgstr "Autre" msgstr "Autre"
@ -780,7 +780,8 @@ msgstr "Opération liée : "
#: core/templates/core/file_edit.jinja:8 #: core/templates/core/file_edit.jinja:8
#: core/templates/core/macros_pages.jinja:25 #: core/templates/core/macros_pages.jinja:25
#: core/templates/core/page_prop.jinja:11 #: core/templates/core/page_prop.jinja:11
#: core/templates/core/user_godfathers.jinja:62 #: core/templates/core/user_godfathers.jinja:61
#: core/templates/core/user_godfathers_tree.jinja:91
#: core/templates/core/user_preferences.jinja:18 #: core/templates/core/user_preferences.jinja:18
#: core/templates/core/user_preferences.jinja:27 #: core/templates/core/user_preferences.jinja:27
#: core/templates/core/user_preferences.jinja:65 #: core/templates/core/user_preferences.jinja:65
@ -1041,11 +1042,11 @@ msgstr "actif"
msgid "short description" msgid "short description"
msgstr "description courte" msgstr "description courte"
#: club/models.py:80 core/models.py:366 #: club/models.py:80 core/models.py:367
msgid "address" msgid "address"
msgstr "Adresse" msgstr "Adresse"
#: club/models.py:97 core/models.py:277 #: club/models.py:97 core/models.py:278
msgid "home" msgid "home"
msgstr "home" msgstr "home"
@ -1064,12 +1065,12 @@ msgstr "Un club avec ce nom UNIX existe déjà."
msgid "user" msgid "user"
msgstr "nom d'utilisateur" msgstr "nom d'utilisateur"
#: club/models.py:353 core/models.py:330 election/models.py:178 #: club/models.py:353 core/models.py:331 election/models.py:178
#: election/models.py:212 trombi/models.py:211 #: election/models.py:212 trombi/models.py:211
msgid "role" msgid "role"
msgstr "rôle" msgstr "rôle"
#: club/models.py:358 core/models.py:88 counter/models.py:209 #: club/models.py:358 core/models.py:89 counter/models.py:209
#: counter/models.py:240 election/models.py:13 election/models.py:115 #: counter/models.py:240 election/models.py:13 election/models.py:115
#: election/models.py:188 forum/models.py:60 forum/models.py:244 #: election/models.py:188 forum/models.py:60 forum/models.py:244
msgid "description" msgid "description"
@ -1084,7 +1085,7 @@ msgid "Enter a valid address. Only the root of the address is needed."
msgstr "" msgstr ""
"Entrez une adresse valide. Seule la racine de l'adresse est nécessaire." "Entrez une adresse valide. Seule la racine de l'adresse est nécessaire."
#: club/models.py:428 com/models.py:82 com/models.py:309 core/models.py:905 #: club/models.py:428 com/models.py:82 com/models.py:309 core/models.py:941
msgid "is moderated" msgid "is moderated"
msgstr "est modéré" msgstr "est modéré"
@ -1439,7 +1440,7 @@ msgstr "résumé"
msgid "content" msgid "content"
msgstr "contenu" msgstr "contenu"
#: com/models.py:71 core/models.py:1474 launderette/models.py:92 #: com/models.py:71 core/models.py:1510 launderette/models.py:92
#: launderette/models.py:130 launderette/models.py:181 stock/models.py:74 #: launderette/models.py:130 launderette/models.py:181 stock/models.py:74
#: stock/models.py:129 #: stock/models.py:129
msgid "type" msgid "type"
@ -1490,7 +1491,7 @@ msgstr "weekmail"
msgid "rank" msgid "rank"
msgstr "rang" msgstr "rang"
#: com/models.py:295 core/models.py:870 core/models.py:920 #: com/models.py:295 core/models.py:906 core/models.py:956
msgid "file" msgid "file"
msgstr "fichier" msgstr "fichier"
@ -1975,30 +1976,30 @@ msgstr ""
"Vous devez êtres un membre du bureau du club sélectionné pour poster dans le " "Vous devez êtres un membre du bureau du club sélectionné pour poster dans le "
"Weekmail." "Weekmail."
#: core/models.py:83 #: core/models.py:84
msgid "meta group status" msgid "meta group status"
msgstr "status du meta-groupe" msgstr "status du meta-groupe"
#: core/models.py:85 #: core/models.py:86
msgid "Whether a group is a meta group or not" msgid "Whether a group is a meta group or not"
msgstr "Si un groupe est un meta-groupe ou pas" msgstr "Si un groupe est un meta-groupe ou pas"
#: core/models.py:171 #: core/models.py:172
#, python-format #, python-format
msgid "%(value)s is not a valid promo (between 0 and %(end)s)" msgid "%(value)s is not a valid promo (between 0 and %(end)s)"
msgstr "%(value)s n'est pas une promo valide (doit être entre 0 et %(end)s)" msgstr "%(value)s n'est pas une promo valide (doit être entre 0 et %(end)s)"
#: core/models.py:230 #: core/models.py:231
msgid "username" msgid "username"
msgstr "nom d'utilisateur" msgstr "nom d'utilisateur"
#: core/models.py:234 #: core/models.py:235
msgid "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." msgid "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only."
msgstr "" msgstr ""
"Requis. Pas plus de 254 caractères. Uniquement des lettres, numéros, et ./" "Requis. Pas plus de 254 caractères. Uniquement des lettres, numéros, et ./"
"+/-/_" "+/-/_"
#: core/models.py:240 #: core/models.py:241
msgid "" msgid ""
"Enter a valid username. This value may contain only letters, numbers and ./" "Enter a valid username. This value may contain only letters, numbers and ./"
"+/-/_ characters." "+/-/_ characters."
@ -2006,43 +2007,43 @@ msgstr ""
"Entrez un nom d'utilisateur correct. Uniquement des lettres, numéros, et ./" "Entrez un nom d'utilisateur correct. Uniquement des lettres, numéros, et ./"
"+/-/_" "+/-/_"
#: core/models.py:246 #: core/models.py:247
msgid "A user with that username already exists." msgid "A user with that username already exists."
msgstr "Un utilisateur de ce nom existe déjà" msgstr "Un utilisateur de ce nom existe déjà"
#: core/models.py:248 #: core/models.py:249
msgid "first name" msgid "first name"
msgstr "Prénom" msgstr "Prénom"
#: core/models.py:249 #: core/models.py:250
msgid "last name" msgid "last name"
msgstr "Nom" msgstr "Nom"
#: core/models.py:250 #: core/models.py:251
msgid "email address" msgid "email address"
msgstr "adresse email" msgstr "adresse email"
#: core/models.py:251 #: core/models.py:252
msgid "date of birth" msgid "date of birth"
msgstr "date de naissance" msgstr "date de naissance"
#: core/models.py:252 #: core/models.py:253
msgid "nick name" msgid "nick name"
msgstr "surnom" msgstr "surnom"
#: core/models.py:254 #: core/models.py:255
msgid "staff status" msgid "staff status"
msgstr "status \"staff\"" msgstr "status \"staff\""
#: core/models.py:256 #: core/models.py:257
msgid "Designates whether the user can log into this admin site." msgid "Designates whether the user can log into this admin site."
msgstr "Est-ce que l'utilisateur peut se logger à la partie admin du site." msgstr "Est-ce que l'utilisateur peut se logger à la partie admin du site."
#: core/models.py:259 #: core/models.py:260
msgid "active" msgid "active"
msgstr "actif" msgstr "actif"
#: core/models.py:262 #: core/models.py:263
msgid "" msgid ""
"Designates whether this user should be treated as active. Unselect this " "Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts." "instead of deleting accounts."
@ -2050,163 +2051,163 @@ msgstr ""
"Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au " "Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au "
"lieu de supprimer les comptes." "lieu de supprimer les comptes."
#: core/models.py:266 #: core/models.py:267
msgid "date joined" msgid "date joined"
msgstr "date d'inscription" msgstr "date d'inscription"
#: core/models.py:267 #: core/models.py:268
msgid "last update" msgid "last update"
msgstr "dernière mise à jour" msgstr "dernière mise à jour"
#: core/models.py:269 #: core/models.py:270
msgid "superuser" msgid "superuser"
msgstr "super-utilisateur" msgstr "super-utilisateur"
#: core/models.py:271 #: core/models.py:272
msgid "Designates whether this user is a superuser. " msgid "Designates whether this user is a superuser. "
msgstr "Est-ce que l'utilisateur est super-utilisateur." msgstr "Est-ce que l'utilisateur est super-utilisateur."
#: core/models.py:285 #: core/models.py:286
msgid "profile" msgid "profile"
msgstr "profil" msgstr "profil"
#: core/models.py:293 #: core/models.py:294
msgid "avatar" msgid "avatar"
msgstr "avatar" msgstr "avatar"
#: core/models.py:301 #: core/models.py:302
msgid "scrub" msgid "scrub"
msgstr "blouse" msgstr "blouse"
#: core/models.py:307 #: core/models.py:308
msgid "sex" msgid "sex"
msgstr "Genre" msgstr "Genre"
#: core/models.py:311 #: core/models.py:312
msgid "Man" msgid "Man"
msgstr "Homme" msgstr "Homme"
#: core/models.py:311 #: core/models.py:312
msgid "Woman" msgid "Woman"
msgstr "Femme" msgstr "Femme"
#: core/models.py:313 #: core/models.py:314
msgid "pronouns" msgid "pronouns"
msgstr "pronoms" msgstr "pronoms"
#: core/models.py:315 #: core/models.py:316
msgid "tshirt size" msgid "tshirt size"
msgstr "taille de t-shirt" msgstr "taille de t-shirt"
#: core/models.py:318 #: core/models.py:319
msgid "-" msgid "-"
msgstr "-" msgstr "-"
#: core/models.py:319 #: core/models.py:320
msgid "XS" msgid "XS"
msgstr "XS" msgstr "XS"
#: core/models.py:320 #: core/models.py:321
msgid "S" msgid "S"
msgstr "S" msgstr "S"
#: core/models.py:321 #: core/models.py:322
msgid "M" msgid "M"
msgstr "M" msgstr "M"
#: core/models.py:322 #: core/models.py:323
msgid "L" msgid "L"
msgstr "L" msgstr "L"
#: core/models.py:323 #: core/models.py:324
msgid "XL" msgid "XL"
msgstr "XL" msgstr "XL"
#: core/models.py:324 #: core/models.py:325
msgid "XXL" msgid "XXL"
msgstr "XXL" msgstr "XXL"
#: core/models.py:325 #: core/models.py:326
msgid "XXXL" msgid "XXXL"
msgstr "XXXL" msgstr "XXXL"
#: core/models.py:333 #: core/models.py:334
msgid "Student" msgid "Student"
msgstr "Étudiant" msgstr "Étudiant"
#: core/models.py:334 #: core/models.py:335
msgid "Administrative agent" msgid "Administrative agent"
msgstr "Personnel administratif" msgstr "Personnel administratif"
#: core/models.py:335 #: core/models.py:336
msgid "Teacher" msgid "Teacher"
msgstr "Enseignant" msgstr "Enseignant"
#: core/models.py:336 #: core/models.py:337
msgid "Agent" msgid "Agent"
msgstr "Personnel" msgstr "Personnel"
#: core/models.py:337 #: core/models.py:338
msgid "Doctor" msgid "Doctor"
msgstr "Doctorant" msgstr "Doctorant"
#: core/models.py:338 #: core/models.py:339
msgid "Former student" msgid "Former student"
msgstr "Ancien étudiant" msgstr "Ancien étudiant"
#: core/models.py:339 #: core/models.py:340
msgid "Service" msgid "Service"
msgstr "Service" msgstr "Service"
#: core/models.py:345 #: core/models.py:346
msgid "department" msgid "department"
msgstr "département" msgstr "département"
#: core/models.py:352 #: core/models.py:353
msgid "dpt option" msgid "dpt option"
msgstr "Filière" msgstr "Filière"
#: core/models.py:354 pedagogy/models.py:69 pedagogy/models.py:293 #: core/models.py:355 pedagogy/models.py:69 pedagogy/models.py:293
msgid "semester" msgid "semester"
msgstr "semestre" msgstr "semestre"
#: core/models.py:355 #: core/models.py:356
msgid "quote" msgid "quote"
msgstr "citation" msgstr "citation"
#: core/models.py:356 #: core/models.py:357
msgid "school" msgid "school"
msgstr "école" msgstr "école"
#: core/models.py:358 #: core/models.py:359
msgid "promo" msgid "promo"
msgstr "promo" msgstr "promo"
#: core/models.py:361 #: core/models.py:362
msgid "forum signature" msgid "forum signature"
msgstr "signature du forum" msgstr "signature du forum"
#: core/models.py:363 #: core/models.py:364
msgid "second email address" msgid "second email address"
msgstr "adresse email secondaire" msgstr "adresse email secondaire"
#: core/models.py:365 #: core/models.py:366
msgid "parent phone" msgid "parent phone"
msgstr "téléphone des parents" msgstr "téléphone des parents"
#: core/models.py:368 #: core/models.py:369
msgid "parent address" msgid "parent address"
msgstr "adresse des parents" msgstr "adresse des parents"
#: core/models.py:371 #: core/models.py:372
msgid "is subscriber viewable" msgid "is subscriber viewable"
msgstr "profil visible par les cotisants" msgstr "profil visible par les cotisants"
#: core/models.py:571 #: core/models.py:572
msgid "A user with that username already exists" msgid "A user with that username already exists"
msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
#: core/models.py:701 core/templates/core/macros.jinja:75 #: core/models.py:737 core/templates/core/macros.jinja:75
#: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78 #: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78
#: core/templates/core/user_detail.jinja:100 #: core/templates/core/user_detail.jinja:100
#: core/templates/core/user_detail.jinja:101 #: core/templates/core/user_detail.jinja:101
@ -2225,101 +2226,101 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
msgid "Profile" msgid "Profile"
msgstr "Profil" msgstr "Profil"
#: core/models.py:820 #: core/models.py:856
msgid "Visitor" msgid "Visitor"
msgstr "Visiteur" msgstr "Visiteur"
#: core/models.py:827 #: core/models.py:863
msgid "receive the Weekmail" msgid "receive the Weekmail"
msgstr "recevoir le Weekmail" msgstr "recevoir le Weekmail"
#: core/models.py:828 #: core/models.py:864
msgid "show your stats to others" msgid "show your stats to others"
msgstr "montrez vos statistiques aux autres" msgstr "montrez vos statistiques aux autres"
#: core/models.py:830 #: core/models.py:866
msgid "get a notification for every click" msgid "get a notification for every click"
msgstr "avoir une notification pour chaque click" msgstr "avoir une notification pour chaque click"
#: core/models.py:833 #: core/models.py:869
msgid "get a notification for every refilling" msgid "get a notification for every refilling"
msgstr "avoir une notification pour chaque rechargement" msgstr "avoir une notification pour chaque rechargement"
#: core/models.py:859 sas/views.py:356 #: core/models.py:895 sas/views.py:356
msgid "file name" msgid "file name"
msgstr "nom du fichier" msgstr "nom du fichier"
#: core/models.py:863 core/models.py:1223 #: core/models.py:899 core/models.py:1259
msgid "parent" msgid "parent"
msgstr "parent" msgstr "parent"
#: core/models.py:877 #: core/models.py:913
msgid "compressed file" msgid "compressed file"
msgstr "version allégée" msgstr "version allégée"
#: core/models.py:884 #: core/models.py:920
msgid "thumbnail" msgid "thumbnail"
msgstr "miniature" msgstr "miniature"
#: core/models.py:892 core/models.py:909 #: core/models.py:928 core/models.py:945
msgid "owner" msgid "owner"
msgstr "propriétaire" msgstr "propriétaire"
#: core/models.py:896 core/models.py:1240 core/views/files.py:230 #: core/models.py:932 core/models.py:1276 core/views/files.py:230
msgid "edit group" msgid "edit group"
msgstr "groupe d'édition" msgstr "groupe d'édition"
#: core/models.py:899 core/models.py:1243 core/views/files.py:233 #: core/models.py:935 core/models.py:1279 core/views/files.py:233
msgid "view group" msgid "view group"
msgstr "groupe de vue" msgstr "groupe de vue"
#: core/models.py:901 #: core/models.py:937
msgid "is folder" msgid "is folder"
msgstr "est un dossier" msgstr "est un dossier"
#: core/models.py:902 #: core/models.py:938
msgid "mime type" msgid "mime type"
msgstr "type mime" msgstr "type mime"
#: core/models.py:903 #: core/models.py:939
msgid "size" msgid "size"
msgstr "taille" msgstr "taille"
#: core/models.py:914 #: core/models.py:950
msgid "asked for removal" msgid "asked for removal"
msgstr "retrait demandé" msgstr "retrait demandé"
#: core/models.py:916 #: core/models.py:952
msgid "is in the SAS" msgid "is in the SAS"
msgstr "est dans le SAS" msgstr "est dans le SAS"
#: core/models.py:1010 #: core/models.py:1046
msgid "Character '/' not authorized in name" msgid "Character '/' not authorized in name"
msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier"
#: core/models.py:1012 core/models.py:1016 #: core/models.py:1048 core/models.py:1052
msgid "Loop in folder tree" msgid "Loop in folder tree"
msgstr "Boucle dans l'arborescence des dossiers" msgstr "Boucle dans l'arborescence des dossiers"
#: core/models.py:1019 #: core/models.py:1055
msgid "You can not make a file be a children of a non folder file" msgid "You can not make a file be a children of a non folder file"
msgstr "" msgstr ""
"Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas " "Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas "
"un dossier" "un dossier"
#: core/models.py:1030 #: core/models.py:1066
msgid "Duplicate file" msgid "Duplicate file"
msgstr "Un fichier de ce nom existe déjà" msgstr "Un fichier de ce nom existe déjà"
#: core/models.py:1047 #: core/models.py:1083
msgid "You must provide a file" msgid "You must provide a file"
msgstr "Vous devez fournir un fichier" msgstr "Vous devez fournir un fichier"
#: core/models.py:1206 #: core/models.py:1242
msgid "page unix name" msgid "page unix name"
msgstr "nom unix de la page" msgstr "nom unix de la page"
#: core/models.py:1212 #: core/models.py:1248
msgid "" msgid ""
"Enter a valid page name. This value may contain only unaccented letters, " "Enter a valid page name. This value may contain only unaccented letters, "
"numbers and ./+/-/_ characters." "numbers and ./+/-/_ characters."
@ -2327,55 +2328,55 @@ msgstr ""
"Entrez un nom de page correct. Uniquement des lettres non accentuées, " "Entrez un nom de page correct. Uniquement des lettres non accentuées, "
"numéros, et ./+/-/_" "numéros, et ./+/-/_"
#: core/models.py:1230 #: core/models.py:1266
msgid "page name" msgid "page name"
msgstr "nom de la page" msgstr "nom de la page"
#: core/models.py:1235 #: core/models.py:1271
msgid "owner group" msgid "owner group"
msgstr "groupe propriétaire" msgstr "groupe propriétaire"
#: core/models.py:1248 #: core/models.py:1284
msgid "lock user" msgid "lock user"
msgstr "utilisateur bloquant" msgstr "utilisateur bloquant"
#: core/models.py:1255 #: core/models.py:1291
msgid "lock_timeout" msgid "lock_timeout"
msgstr "décompte du déblocage" msgstr "décompte du déblocage"
#: core/models.py:1305 #: core/models.py:1341
msgid "Duplicate page" msgid "Duplicate page"
msgstr "Une page de ce nom existe déjà" msgstr "Une page de ce nom existe déjà"
#: core/models.py:1308 #: core/models.py:1344
msgid "Loop in page tree" msgid "Loop in page tree"
msgstr "Boucle dans l'arborescence des pages" msgstr "Boucle dans l'arborescence des pages"
#: core/models.py:1428 #: core/models.py:1464
msgid "revision" msgid "revision"
msgstr "révision" msgstr "révision"
#: core/models.py:1429 #: core/models.py:1465
msgid "page title" msgid "page title"
msgstr "titre de la page" msgstr "titre de la page"
#: core/models.py:1430 #: core/models.py:1466
msgid "page content" msgid "page content"
msgstr "contenu de la page" msgstr "contenu de la page"
#: core/models.py:1471 #: core/models.py:1507
msgid "url" msgid "url"
msgstr "url" msgstr "url"
#: core/models.py:1472 #: core/models.py:1508
msgid "param" msgid "param"
msgstr "param" msgstr "param"
#: core/models.py:1477 #: core/models.py:1513
msgid "viewed" msgid "viewed"
msgstr "vue" msgstr "vue"
#: core/models.py:1535 #: core/models.py:1571
msgid "operation type" msgid "operation type"
msgstr "type d'opération" msgstr "type d'opération"
@ -2906,8 +2907,9 @@ msgstr "Vous avez correctement changé votre mot de passe !"
#: core/templates/core/password_reset.jinja:8 #: core/templates/core/password_reset.jinja:8
#: core/templates/core/password_reset_confirm.jinja:8 #: core/templates/core/password_reset_confirm.jinja:8
#: core/templates/core/user_godfathers_tree.jinja:87
msgid "Reset" msgid "Reset"
msgstr "Reset" msgstr "Réinitialiser"
#: core/templates/core/password_reset_complete.jinja:4 #: core/templates/core/password_reset_complete.jinja:4
msgid "You successfully reset your password!" msgid "You successfully reset your password!"
@ -3223,68 +3225,44 @@ msgstr "Numéro de compte : "
msgid "%(user_name)s's family" msgid "%(user_name)s's family"
msgstr "Famille de %(user_name)s" msgstr "Famille de %(user_name)s"
#: core/templates/core/user_godfathers.jinja:15 #: core/templates/core/user_godfathers.jinja:20
msgid "Show family picture" msgid "Show family tree"
msgstr "Voir une image de la famille" msgstr "Afficher l'arbre généalogique"
#: core/templates/core/user_godfathers.jinja:18 #: core/templates/core/user_godfathers.jinja:24
msgid "Godfathers / Godmothers" msgid "Godfathers / Godmothers"
msgstr "Parrains / Marraines" msgstr "Parrains / Marraines"
#: core/templates/core/user_godfathers.jinja:32 #: core/templates/core/user_godfathers.jinja:38
msgid "Show ancestors tree"
msgstr "Voir l'arbre des ancêtres"
#: core/templates/core/user_godfathers.jinja:35
#: core/templates/core/user_godfathers_tree.jinja:50
msgid "No godfathers / godmothers" msgid "No godfathers / godmothers"
msgstr "Pas de famille" msgstr "Pas de famille"
#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:439 #: core/templates/core/user_godfathers.jinja:41
msgid "Godchildren" msgid "Godchildren"
msgstr "Fillots / Fillotes" msgstr "Fillots / Fillotes"
#: core/templates/core/user_godfathers.jinja:52 #: core/templates/core/user_godfathers.jinja:54
msgid "Show descent tree"
msgstr "Voir l'arbre de la descendance"
#: core/templates/core/user_godfathers.jinja:55
#: core/templates/core/user_godfathers_tree.jinja:48
msgid "No godchildren" msgid "No godchildren"
msgstr "Pas de fillots / fillotes" msgstr "Pas de fillots / fillotes"
#: core/templates/core/user_godfathers_tree.jinja:5
#, python-format
msgid "%(user_name)s's godchildren"
msgstr "Fillots/Fillotes de %(user_name)s"
#: core/templates/core/user_godfathers_tree.jinja:7
#, python-format
msgid "%(user_name)s's godfathers"
msgstr "Parrains/Marraines de %(user_name)s"
#: core/templates/core/user_godfathers_tree.jinja:20 #: core/templates/core/user_godfathers_tree.jinja:20
msgid "Already seen (check above)"
msgstr "Déjà vu (voir plus haut)"
#: core/templates/core/user_godfathers_tree.jinja:33
msgid "Back to family"
msgstr "Retour à la famille"
#: core/templates/core/user_godfathers_tree.jinja:37
#: core/templates/core/user_godfathers_tree.jinja:41
msgid "Show a picture of the tree"
msgstr "Voir une image de l'arbre"
#: core/templates/core/user_godfathers_tree.jinja:38
#, python-format #, python-format
msgid "Descent tree of %(u)s" msgid "%(user_name)s's family tree"
msgstr "Descendants de %(u)s" msgstr "Arbre généalogique de %(user_name)s"
#: core/templates/core/user_godfathers_tree.jinja:42 #: core/templates/core/user_godfathers_tree.jinja:29
#, python-format #, python-format
msgid "Ancestors tree of %(u)s" msgid "Max godfather depth between %(min)s and %(max)s"
msgstr "Ancêtres de %(u)s" msgstr "Maximum de profondeur pour les parrains entre %(min)s et %(max)s"
#: core/templates/core/user_godfathers_tree.jinja:55
#, python-format
msgid "Max godchildren depth between %(min)s and %(max)s"
msgstr "Maximum de profondeur pour les fillots entre %(min)s et %(max)s"
#: core/templates/core/user_godfathers_tree.jinja:83
msgid "Reverse"
msgstr "Inverser"
#: core/templates/core/user_group.jinja:9 #: core/templates/core/user_group.jinja:9
#, python-format #, python-format
@ -3680,12 +3658,31 @@ msgstr "Fillot / Fillote"
msgid "Select user" msgid "Select user"
msgstr "Choisir un utilisateur" msgstr "Choisir un utilisateur"
#: core/views/forms.py:351 core/views/forms.py:369 election/models.py:22 #: core/views/forms.py:348
msgid "This user does not exist"
msgstr "Cet utilisateur n'existe pas"
#: core/views/forms.py:350
msgid "You cannot be related to yourself"
msgstr "Vous ne pouvez pas être relié à vous-même"
#: core/views/forms.py:362
#, python-format
msgid "%s is already your godfather"
msgstr "%s est déjà votre parrain/marraine"
#: core/views/forms.py:368
#, fuzzy, python-format
#| msgid "This user has already commented on this UV"
msgid "%s is already your godchild"
msgstr "%s est déjà votre fillot/fillote"
#: core/views/forms.py:382 core/views/forms.py:400 election/models.py:22
#: election/views.py:147 #: election/views.py:147
msgid "edit groups" msgid "edit groups"
msgstr "groupe d'édition" msgstr "groupe d'édition"
#: core/views/forms.py:354 core/views/forms.py:372 election/models.py:29 #: core/views/forms.py:385 core/views/forms.py:403 election/models.py:29
#: election/views.py:150 #: election/views.py:150
msgid "view groups" msgid "view groups"
msgstr "groupe de vue" msgstr "groupe de vue"
@ -3702,7 +3699,7 @@ msgstr "Utilisateurs à ajouter au groupe"
msgid "We couldn't verify that this email actually exists" msgid "We couldn't verify that this email actually exists"
msgstr "Nous n'avons pas réussi à vérifier que cette adresse mail existe." msgstr "Nous n'avons pas réussi à vérifier que cette adresse mail existe."
#: core/views/user.py:202 core/views/user.py:441 core/views/user.py:443 #: core/views/user.py:202
msgid "Family" msgid "Family"
msgstr "Famille" msgstr "Famille"
@ -6313,85 +6310,3 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
#, python-format #, python-format
msgid "Maximum characters: %(max_length)s" msgid "Maximum characters: %(max_length)s"
msgstr "Nombre de caractères max: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s"
#~ msgid "Picture Unavailable"
#~ msgstr "Photo Indisponible"
#~ msgid "Convert dokuwiki/BBcode syntax to Markdown"
#~ msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown"
#~ msgid "This album does not contain any photos."
#~ msgstr "Cet album ne contient aucune photo."
#~ msgid "You have no basket."
#~ msgstr "Vous n'avez pas de panier."
#~ msgid "The basket cookie was badly formatted."
#~ msgstr "Le cookie du panier a été mal formaté."
#, python-format
#~ msgid "%(name)s : this product does not exist."
#~ msgstr "%(name)s : ce produit n'existe pas."
#, python-format
#~ msgid "You cannot buy %(nbr)d %(name)s."
#~ msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s."
#~ msgid "Error downloading your pictures"
#~ msgstr "Erreur de téléchargement de vos photos"
#~ msgid "Error connecting to the server"
#~ msgstr "Erreur lors de la connection au serveur"
#~ msgid "past member"
#~ msgstr "Anciens membres"
#, python-format
#~ msgid "Welcome %(user_name)s!"
#~ msgstr "Bienvenue, %(user_name)s!"
#~ msgid ""
#~ "You successfully registered and you will soon receive a confirmation mail."
#~ msgstr ""
#~ "Vous vous êtes correctement enregistré, et vous devriez recevoir "
#~ "rapidement un email de confirmation."
#, python-format
#~ msgid "Your username is %(username)s."
#~ msgstr "Votre nom d'utilisateur est %(username)s."
#~ msgid "Subscription error"
#~ msgstr "Erreur de cotisation"
#~ msgid "Folder: "
#~ msgstr "Dossier : "
#~ msgid "File: "
#~ msgstr "Fichier : "
#~ msgid "Username"
#~ msgstr "Nom d'utilisateur"
#~ msgid "Password"
#~ msgstr "Mot de passe"
#~ msgid "Register a user"
#~ msgstr "Enregistrer un utilisateur"
#~ msgid "Current profile: "
#~ msgstr "Profil actuel : "
#~ msgid "Current avatar: "
#~ msgstr "Avatar actuel : "
#~ msgid "Current scrub: "
#~ msgstr "Blouse actuelle : "
#~ msgid "preview"
#~ msgstr "miniature"
#~ msgid "Rotate left"
#~ msgstr "Tourner vers la gauche"
#~ msgid "Rotate right"
#~ msgstr "Tourner vers la droite"

View File

@ -0,0 +1,21 @@
# Sith AE french frontend translation file
# Copyright (C) 2024
# This file is distributed under the same license as the Sith package.
# ae@utbm.fr / ae.info@utbm.fr
#
#, fuzzy
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-17 11:54+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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: core/static/user/js/family_graph.js:230
msgid "family_tree.%(extension)s"
msgstr "arbre_genealogique.%(extension)s"

513
poetry.lock generated
View File

@ -92,78 +92,78 @@ files = [
[[package]] [[package]]
name = "cffi" name = "cffi"
version = "1.17.0" version = "1.17.1"
description = "Foreign Function Interface for Python calling C code." description = "Foreign Function Interface for Python calling C code."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
{file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
{file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
{file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
{file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
{file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
{file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
{file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
{file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
{file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
{file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
{file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
{file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
{file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
{file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
{file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
{file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
{file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
{file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
{file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
{file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
{file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
{file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
{file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
] ]
[package.dependencies] [package.dependencies]
@ -412,38 +412,38 @@ toml = ["tomli"]
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "43.0.0" version = "43.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"},
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"},
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"},
{file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"},
{file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"},
{file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"},
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"},
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"},
{file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"},
{file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"},
{file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"},
] ]
[package.dependencies] [package.dependencies]
@ -456,7 +456,7 @@ nox = ["nox"]
pep8test = ["check-sdist", "click", "mypy", "ruff"] pep8test = ["check-sdist", "click", "mypy", "ruff"]
sdist = ["build"] sdist = ["build"]
ssh = ["bcrypt (>=3.1.5)"] ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"] test-randomorder = ["pytest-randomly"]
[[package]] [[package]]
@ -497,13 +497,13 @@ files = [
[[package]] [[package]]
name = "django" name = "django"
version = "4.2.15" version = "4.2.16"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "Django-4.2.15-py3-none-any.whl", hash = "sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30"}, {file = "Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898"},
{file = "Django-4.2.15.tar.gz", hash = "sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a"}, {file = "Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad"},
] ]
[package.dependencies] [package.dependencies]
@ -761,19 +761,19 @@ python-dateutil = ">=2.4"
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.15.4" version = "3.16.0"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"},
{file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"},
] ]
[package.extras] [package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"]
typing = ["typing-extensions (>=4.8)"] typing = ["typing-extensions (>=4.12.2)"]
[[package]] [[package]]
name = "freezegun" name = "freezegun"
@ -1450,13 +1450,13 @@ ptyprocess = ">=0.5"
[[package]] [[package]]
name = "phonenumbers" name = "phonenumbers"
version = "8.13.44" version = "8.13.45"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "phonenumbers-8.13.44-py2.py3-none-any.whl", hash = "sha256:52cd02865dab1428ca9e89d442629b61d407c7dc687cfb80a3e8d068a584513c"}, {file = "phonenumbers-8.13.45-py2.py3-none-any.whl", hash = "sha256:bf05ec20fcd13f0d53e43a34ed7bd1c8be26a72b88fce4b8c64fca5b4641987a"},
{file = "phonenumbers-8.13.44.tar.gz", hash = "sha256:2175021e84ee4e41b43c890f2d0af51f18c6ca9ad525886d6d6e4ea882e46fac"}, {file = "phonenumbers-8.13.45.tar.gz", hash = "sha256:53679a95b6060fd5e15467759252c87933d8566d6a5be00995a579eb0e02435b"},
] ]
[[package]] [[package]]
@ -1558,19 +1558,19 @@ xmp = ["defusedxml"]
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.2.2" version = "4.3.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"},
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, {file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"},
] ]
[package.extras] [package.extras]
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
type = ["mypy (>=1.8)"] type = ["mypy (>=1.11.2)"]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
@ -1691,18 +1691,18 @@ files = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.8.2" version = "2.9.1"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"},
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"},
] ]
[package.dependencies] [package.dependencies]
annotated-types = ">=0.4.0" annotated-types = ">=0.6.0"
pydantic-core = "2.20.1" pydantic-core = "2.23.3"
typing-extensions = [ typing-extensions = [
{version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.12.2", markers = "python_version >= \"3.13\""},
{version = ">=4.6.1", markers = "python_version < \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""},
@ -1710,103 +1710,104 @@ typing-extensions = [
[package.extras] [package.extras]
email = ["email-validator (>=2.0.0)"] email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata"]
[[package]] [[package]]
name = "pydantic-core" name = "pydantic-core"
version = "2.20.1" version = "2.23.3"
description = "Core functionality for Pydantic validation and serialization" description = "Core functionality for Pydantic validation and serialization"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"},
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"},
{file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"},
{file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"},
{file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"},
{file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"},
{file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"},
{file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"},
{file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"},
{file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"},
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"},
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"},
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"},
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"},
{file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"},
{file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"},
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"},
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"},
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"},
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"},
{file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"},
{file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"},
{file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"},
] ]
[package.dependencies] [package.dependencies]
@ -1826,16 +1827,6 @@ files = [
[package.extras] [package.extras]
windows-terminal = ["colorama (>=0.4.6)"] windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pygraphviz"
version = "1.13"
description = "Python interface to Graphviz"
optional = false
python-versions = ">=3.10"
files = [
{file = "pygraphviz-1.13.tar.gz", hash = "sha256:6ad8aa2f26768830a5a1cfc8a14f022d13df170a8f6fdfd68fd1aa1267000964"},
]
[[package]] [[package]]
name = "pymdown-extensions" name = "pymdown-extensions"
version = "10.9" version = "10.9"
@ -1894,13 +1885,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
[[package]] [[package]]
name = "pytest-django" name = "pytest-django"
version = "4.8.0" version = "4.9.0"
description = "A Django plugin for pytest." description = "A Django plugin for pytest."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pytest-django-4.8.0.tar.gz", hash = "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90"}, {file = "pytest_django-4.9.0-py3-none-any.whl", hash = "sha256:1d83692cb39188682dbb419ff0393867e9904094a549a7d38a3154d5731b2b99"},
{file = "pytest_django-4.8.0-py3-none-any.whl", hash = "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7"}, {file = "pytest_django-4.9.0.tar.gz", hash = "sha256:8bf7bc358c9ae6f6fc51b6cebb190fe20212196e6807121f11bd6a3b03428314"},
] ]
[package.dependencies] [package.dependencies]
@ -2232,13 +2223,13 @@ files = [
[[package]] [[package]]
name = "sentry-sdk" name = "sentry-sdk"
version = "2.13.0" version = "2.14.0"
description = "Python client for Sentry (https://sentry.io)" description = "Python client for Sentry (https://sentry.io)"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "sentry_sdk-2.13.0-py2.py3-none-any.whl", hash = "sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6"}, {file = "sentry_sdk-2.14.0-py2.py3-none-any.whl", hash = "sha256:b8bc3dc51d06590df1291b7519b85c75e2ced4f28d9ea655b6d54033503b5bf4"},
{file = "sentry_sdk-2.13.0.tar.gz", hash = "sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260"}, {file = "sentry_sdk-2.14.0.tar.gz", hash = "sha256:1e0e2eaf6dad918c7d1e0edac868a7bf20017b177f242cefe2a6bcd47955961d"},
] ]
[package.dependencies] [package.dependencies]
@ -2532,13 +2523,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.26.3" version = "20.26.4"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"},
{file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"},
] ]
[package.dependencies] [package.dependencies]
@ -2552,41 +2543,41 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[[package]] [[package]]
name = "watchdog" name = "watchdog"
version = "5.0.0" version = "5.0.2"
description = "Filesystem events monitoring" description = "Filesystem events monitoring"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "watchdog-5.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf3216ec994eabb2212df9861f19056ca0d4cd3516d56cb95801933876519bfe"}, {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d961f4123bb3c447d9fcdcb67e1530c366f10ab3a0c7d1c0c9943050936d4877"},
{file = "watchdog-5.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb59ad83a1700304fc1ac7bc53ae9e5cbe9d60a52ed9bba8e2e2d782a201bb2b"}, {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72990192cb63872c47d5e5fefe230a401b87fd59d257ee577d61c9e5564c62e5"},
{file = "watchdog-5.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1228cb097e855d1798b550be8f0e9f0cfbac4384f9a3e91f66d250d03e11294e"}, {file = "watchdog-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bec703ad90b35a848e05e1b40bf0050da7ca28ead7ac4be724ae5ac2653a1a0"},
{file = "watchdog-5.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3c177085c3d210d1c73cb4569442bdaef706ebebc423bd7aed9e90fc12b2e553"}, {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae7a1879918f6544201d33666909b040a46421054a50e0f773e0d870ed7438d"},
{file = "watchdog-5.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01ab36cddc836a0f202c66267daaef92ba5c17c7d6436deff0587bb61234c5c9"}, {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4a440f725f3b99133de610bfec93d570b13826f89616377715b9cd60424db6e"},
{file = "watchdog-5.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0834c21efa3e767849b09e667274604c7cdfe30b49eb95d794565c53f4db3c1e"}, {file = "watchdog-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b2918c19e0d48f5f20df458c84692e2a054f02d9df25e6c3c930063eca64c1"},
{file = "watchdog-5.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1e26f570dd7f5178656affb24d6f0e22ce66c8daf88d4061a27bfb9ac866b40d"}, {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa9cd6e24126d4afb3752a3e70fce39f92d0e1a58a236ddf6ee823ff7dba28ee"},
{file = "watchdog-5.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d146331e6b206baa9f6dd40f72b5783ad2302c240df68e7fce196d30588ccf7b"}, {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f627c5bf5759fdd90195b0c0431f99cff4867d212a67b384442c51136a098ed7"},
{file = "watchdog-5.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c96b1706430839872a3e33b9370ee3f7a0079f6b828129d88498ad1f96a0f45"}, {file = "watchdog-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7594a6d32cda2b49df3fd9abf9b37c8d2f3eab5df45c24056b4a671ac661619"},
{file = "watchdog-5.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:663b096368ed7831ac42259919fdb9e0a1f0a8994d972675dfbcca0225e74de1"}, {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba32efcccfe2c58f4d01115440d1672b4eb26cdd6fc5b5818f1fb41f7c3e1889"},
{file = "watchdog-5.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:685931412978d00a91a193d9018fc9e394e565e8e7a0c275512a80e59c6e85f8"}, {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:963f7c4c91e3f51c998eeff1b3fb24a52a8a34da4f956e470f4b068bb47b78ee"},
{file = "watchdog-5.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:109daafc5b0f2a98d1fa9475ff9737eb3559d57b18129a36495e20c71de0b44f"}, {file = "watchdog-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c47150aa12f775e22efff1eee9f0f6beee542a7aa1a985c271b1997d340184f"},
{file = "watchdog-5.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c2b4d90962639ae7cee371ea3a8da506831945d4418eee090c53bc38e6648dc6"}, {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14dd4ed023d79d1f670aa659f449bcd2733c33a35c8ffd88689d9d243885198b"},
{file = "watchdog-5.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e58eafe9cc5ceebe1562cdb89bacdcd0ef470896e8b0139fe677a5abec243da"}, {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84bff0391ad4abe25c2740c7aec0e3de316fdf7764007f41e248422a7760a7f"},
{file = "watchdog-5.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8d747bf6d8fe5ce89cb1a36c3724d1599bd4cde3f90fcba518e6260c7058a52"}, {file = "watchdog-5.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e8d5ff39f0a9968952cce548e8e08f849141a4fcc1290b1c17c032ba697b9d7"},
{file = "watchdog-5.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bc16d448a74a929b896ed9578c25756b2125400b19b3258be8d9a681c7ae8e71"}, {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fb223456db6e5f7bd9bbd5cd969f05aae82ae21acc00643b60d81c770abd402b"},
{file = "watchdog-5.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7e6b0e9b8a9dc3865d65888b5f5222da4ba9c4e09eab13cff5e305e7b7e7248f"}, {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9814adb768c23727a27792c77812cf4e2fd9853cd280eafa2bcfa62a99e8bd6e"},
{file = "watchdog-5.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4fe6780915000743074236b21b6c37419aea71112af62237881bc265589fe463"}, {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:901ee48c23f70193d1a7bc2d9ee297df66081dd5f46f0ca011be4f70dec80dab"},
{file = "watchdog-5.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0710e9502727f688a7e06d48078545c54485b3d6eb53b171810879d8223c362a"}, {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:638bcca3d5b1885c6ec47be67bf712b00a9ab3d4b22ec0881f4889ad870bc7e8"},
{file = "watchdog-5.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d76efab5248aafbf8a2c2a63cd7b9545e6b346ad1397af8b862a3bb3140787d8"}, {file = "watchdog-5.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5597c051587f8757798216f2485e85eac583c3b343e9aa09127a3a6f82c65ee8"},
{file = "watchdog-5.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:ff4e957c45c446de34c513eadce01d0b65da7eee47c01dce472dd136124552c9"}, {file = "watchdog-5.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:53ed1bf71fcb8475dd0ef4912ab139c294c87b903724b6f4a8bd98e026862e6d"},
{file = "watchdog-5.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:16c1aa3377bb1f82c5e24277fcbf4e2cac3c4ce46aaaf7212d53caa9076eb7b7"}, {file = "watchdog-5.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:29e4a2607bd407d9552c502d38b45a05ec26a8e40cc7e94db9bb48f861fa5abc"},
{file = "watchdog-5.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:22fcad6168fc43cf0e709bd854be5b8edbb0b260f0a6f28f1ea9baa53c6907f7"}, {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:b6dc8f1d770a8280997e4beae7b9a75a33b268c59e033e72c8a10990097e5fde"},
{file = "watchdog-5.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0120b2fa65732797ffa65fa8ee5540c288aa861d91447df298626d6385a24658"}, {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:d2ab34adc9bf1489452965cdb16a924e97d4452fcf88a50b21859068b50b5c3b"},
{file = "watchdog-5.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2aa59fab7ff75281778c649557275ca3085eccbdf825a0e2a5ca3810e977afe5"}, {file = "watchdog-5.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:7d1aa7e4bb0f0c65a1a91ba37c10e19dabf7eaaa282c5787e51371f090748f4b"},
{file = "watchdog-5.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:78db0fe0336958fc0e1269545c980b6f33d04d184ba191b2800a8b71d3e971a9"}, {file = "watchdog-5.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:726eef8f8c634ac6584f86c9c53353a010d9f311f6c15a034f3800a7a891d941"},
{file = "watchdog-5.0.0-py3-none-win32.whl", hash = "sha256:d1acef802916083f2ad7988efc7decf07e46e266916c0a09d8fb9d387288ea12"}, {file = "watchdog-5.0.2-py3-none-win32.whl", hash = "sha256:bda40c57115684d0216556671875e008279dea2dc00fcd3dde126ac8e0d7a2fb"},
{file = "watchdog-5.0.0-py3-none-win_amd64.whl", hash = "sha256:3c2d50fdb86aa6df3973313272f5a17eb26eab29ff5a0bf54b6d34597b4dc4e4"}, {file = "watchdog-5.0.2-py3-none-win_amd64.whl", hash = "sha256:d010be060c996db725fbce7e3ef14687cdcc76f4ca0e4339a68cc4532c382a73"},
{file = "watchdog-5.0.0-py3-none-win_ia64.whl", hash = "sha256:1d17ec7e022c34fa7ddc72aa41bf28c9d1207ffb193df18ba4f6fde453725b3c"}, {file = "watchdog-5.0.2-py3-none-win_ia64.whl", hash = "sha256:3960136b2b619510569b90f0cd96408591d6c251a75c97690f4553ca88889769"},
{file = "watchdog-5.0.0.tar.gz", hash = "sha256:990aedb9e2f336b45a70aed9c014450e7c4a70fd99c5f5b1834d57e1453a177e"}, {file = "watchdog-5.0.2.tar.gz", hash = "sha256:dcebf7e475001d2cdeb020be630dc5b687e9acdd60d16fea6bb4508e7b94cf76"},
] ]
[package.extras] [package.extras]
@ -2635,4 +2626,4 @@ filelock = ">=3.4"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "0041e2ec8f5a4ff1f0fca78bb0d6c5865d0a12a92b9869940c389fd524569ba2" content-hash = "b6202203d272cecdb607ea8ebc1ba12dd8369e4f387f65692c4a9681915e6f48"

View File

@ -39,7 +39,6 @@ django-ordered-model = "^3.7"
django-simple-captcha = "^0.6.0" django-simple-captcha = "^0.6.0"
python-dateutil = "^2.8.2" python-dateutil = "^2.8.2"
sentry-sdk = "^2.12.0" sentry-sdk = "^2.12.0"
pygraphviz = "^1.1"
Jinja2 = "^3.1" Jinja2 = "^3.1"
django-countries = "^7.5.1" django-countries = "^7.5.1"
dict2xml = "^1.7.3" dict2xml = "^1.7.3"

View File

@ -751,6 +751,10 @@ SITH_FRONT_DEP_VERSIONS = {
"https://github.com/getsentry/sentry-javascript/": "8.26.0", "https://github.com/getsentry/sentry-javascript/": "8.26.0",
"https://github.com/jhuckaby/webcamjs/": "1.0.0", "https://github.com/jhuckaby/webcamjs/": "1.0.0",
"https://github.com/alpinejs/alpine": "3.14.1", "https://github.com/alpinejs/alpine": "3.14.1",
"https://github.com/cytoscape/cytoscape.js": "3.30.2 ",
"https://github.com/cytoscape/cytoscape.js-cxtmenu": "3.5.0",
"https://github.com/cytoscape/cytoscape.js-klay": "3.1.4",
"https://github.com/kieler/klayjs": "0.4.1", # Deprecated, elk should be used but cytoscape-elk is broken
"https://github.com/mrdoob/three.js/": "r148", "https://github.com/mrdoob/three.js/": "r148",
"https://github.com/vasturiano/three-spritetext": "1.6.5", "https://github.com/vasturiano/three-spritetext": "1.6.5",
"https://github.com/vasturiano/3d-force-graph/": "1.70.19", "https://github.com/vasturiano/3d-force-graph/": "1.70.19",