mirror of
https://github.com/ae-utbm/sith.git
synced 2026-05-22 00:40:22 +00:00
Compare commits
6 Commits
taiste
..
clic-limit
| Author | SHA1 | Date | |
|---|---|---|---|
| 186498d904 | |||
| d4a2b7ec33 | |||
| d35a2945fc | |||
| dbe29669e6 | |||
| 26585aa521 | |||
| 99600341b0 |
@@ -1,7 +1,7 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.15.13
|
||||
rev: v0.15.5
|
||||
hooks:
|
||||
- id: ruff-check # just check the code, and print the errors
|
||||
- id: ruff-check # actually fix the fixable errors, but print nothing
|
||||
@@ -14,7 +14,7 @@ repos:
|
||||
- id: biome-check
|
||||
additional_dependencies: ["@biomejs/biome@2.4.6"]
|
||||
- repo: https://github.com/rtts/djhtml
|
||||
rev: 3.0.11
|
||||
rev: 3.0.10
|
||||
hooks:
|
||||
- id: djhtml
|
||||
name: format templates
|
||||
|
||||
+1
-1
@@ -38,6 +38,6 @@
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"globals": ["Alpine", "gettext", "interpolate"]
|
||||
"globals": ["Alpine", "$", "jQuery", "gettext", "interpolate"]
|
||||
}
|
||||
}
|
||||
|
||||
+1
-16
@@ -16,7 +16,7 @@ from django.contrib import admin
|
||||
from django.forms.models import ModelForm
|
||||
from django.http import HttpRequest
|
||||
|
||||
from club.models import Club, ClubLink, ClubRole, LinkType, Membership
|
||||
from club.models import Club, ClubRole, Membership
|
||||
|
||||
|
||||
@admin.register(Club)
|
||||
@@ -67,18 +67,3 @@ class MembershipAdmin(admin.ModelAdmin):
|
||||
"club__name",
|
||||
)
|
||||
autocomplete_fields = ("user",)
|
||||
|
||||
|
||||
@admin.register(LinkType)
|
||||
class LinkTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "url_base", "icon")
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
@admin.register(ClubLink)
|
||||
class ClubLinkAdmin(admin.ModelAdmin):
|
||||
list_display = ("link_type", "club", "url")
|
||||
list_select_related = ("link_type", "club")
|
||||
autocomplete_fields = ("link_type", "club")
|
||||
search_fields = ("link_type__name", "url")
|
||||
list_filter = ("link_type", ("club", admin.RelatedOnlyFieldListFilter))
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ class ClubController(ControllerBase):
|
||||
queryset=Membership.objects.ongoing().select_related("user", "role"),
|
||||
)
|
||||
return self.get_object_or_exception(
|
||||
Club.objects.prefetch_related(prefetch, "links"), id=club_id
|
||||
Club.objects.prefetch_related(prefetch), id=club_id
|
||||
)
|
||||
|
||||
|
||||
|
||||
+1
-42
@@ -28,14 +28,7 @@ from django.db.models.functions import Lower
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from club.models import (
|
||||
Club,
|
||||
ClubLink,
|
||||
ClubRole,
|
||||
Mailing,
|
||||
MailingSubscription,
|
||||
Membership,
|
||||
)
|
||||
from club.models import Club, ClubRole, Mailing, MailingSubscription, Membership
|
||||
from core.models import User
|
||||
from core.views.forms import SelectDateTime
|
||||
from core.views.widgets.ajax_select import (
|
||||
@@ -46,26 +39,6 @@ from counter.models import Counter, Selling
|
||||
from counter.schemas import SaleFilterSchema
|
||||
|
||||
|
||||
class ClubLinkForm(forms.ModelForm):
|
||||
error_css_class = "error"
|
||||
required_css_class = "required"
|
||||
|
||||
class Meta:
|
||||
model = ClubLink
|
||||
fields = ["url", "name", "link_type"]
|
||||
widgets = {
|
||||
"url": forms.URLInput(
|
||||
{"pattern": "https://.*", "placeholder": "https://monlien.com"}
|
||||
),
|
||||
"link_type": forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
ClubLinkFormSet = forms.inlineformset_factory(
|
||||
Club, ClubLink, ClubLinkForm, extra=0, can_delete_extra=False
|
||||
)
|
||||
|
||||
|
||||
class ClubEditForm(forms.ModelForm):
|
||||
error_css_class = "error"
|
||||
required_css_class = "required"
|
||||
@@ -75,20 +48,6 @@ class ClubEditForm(forms.ModelForm):
|
||||
fields = ["address", "logo", "short_description"]
|
||||
widgets = {"short_description": forms.Textarea()}
|
||||
|
||||
def __init__(self, *args, prefix: str | None = None, instance=None, **kwargs):
|
||||
super().__init__(*args, prefix=prefix, instance=instance, **kwargs)
|
||||
self.link_formset = ClubLinkFormSet(
|
||||
*args, instance=self.instance, prefix="link", **kwargs
|
||||
)
|
||||
|
||||
def is_valid(self):
|
||||
return super().is_valid() and self.link_formset.is_valid()
|
||||
|
||||
def save(self, commit=True): # noqa: FBT002
|
||||
res = super().save(commit=commit)
|
||||
self.link_formset.save(commit=commit)
|
||||
return res
|
||||
|
||||
|
||||
class ClubAdminEditForm(ClubEditForm):
|
||||
admin_fields = ["name", "parent", "is_active"]
|
||||
|
||||
@@ -33,7 +33,7 @@ def migrate_roles(apps: StateApps, schema_editor):
|
||||
club_id=club_id,
|
||||
order=max(SITH_CLUB_ROLES) - role,
|
||||
)
|
||||
updates.append(When(club_id=club_id, role=role, then=new_role.id))
|
||||
updates.append(When(role=role, then=new_role.id))
|
||||
# all updates must happen at the same time
|
||||
# otherwise, the 10 first created ClubRole would be
|
||||
# re-modified after their initial creation, and it would
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
# Generated by Django 5.2.12 on 2026-04-27 07:39
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("club", "0016_clubrole_alter_membership_role")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="LinkType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=40, verbose_name="name")),
|
||||
(
|
||||
"url_base",
|
||||
models.URLField(
|
||||
help_text=(
|
||||
"The base url that links with this type "
|
||||
"must respect (e.g. `https://www.instagram.com`)"
|
||||
),
|
||||
unique=True,
|
||||
verbose_name="url base",
|
||||
),
|
||||
),
|
||||
(
|
||||
"icon",
|
||||
models.CharField(
|
||||
help_text=(
|
||||
"The fontawesome class to use "
|
||||
"(e.g. `fa-brands fa-instagram`)"
|
||||
),
|
||||
max_length=40,
|
||||
verbose_name="icon",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "link type", "verbose_name_plural": "link types"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ClubLink",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(blank=True, max_length=40, verbose_name="name"),
|
||||
),
|
||||
("url", models.URLField(verbose_name="link url")),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created at"),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(auto_now=True, verbose_name="updated at"),
|
||||
),
|
||||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="links",
|
||||
to="club.club",
|
||||
verbose_name="club",
|
||||
),
|
||||
),
|
||||
(
|
||||
"link_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="links",
|
||||
to="club.linktype",
|
||||
verbose_name="link type",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "club link",
|
||||
"verbose_name_plural": "club links",
|
||||
"constraints": [
|
||||
models.UniqueConstraint(
|
||||
fields=["club", "url"],
|
||||
name="club_clublink_unique_club_url",
|
||||
violation_error_message="Duplicated url",
|
||||
)
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -773,81 +773,3 @@ class MailingSubscription(models.Model):
|
||||
|
||||
def fetch_format(self):
|
||||
return self.get_email + " "
|
||||
|
||||
|
||||
class LinkType(models.Model):
|
||||
"""A link type, in order to group links and give them icons.
|
||||
|
||||
Notes:
|
||||
Among all club links, there is a special one, with an empty base url
|
||||
and a default link icon.
|
||||
It is use as a fallback item when no actual link type can be found.
|
||||
|
||||
Danger:
|
||||
LinkType.icon is content that will be raw-rendered in the template.
|
||||
It is NOT safe to allow users to give it.
|
||||
The edition of this field must be reserved to trusted admins.
|
||||
"""
|
||||
|
||||
name = models.CharField(_("name"), max_length=40)
|
||||
url_base = models.URLField(
|
||||
"url base",
|
||||
unique=True,
|
||||
help_text=_(
|
||||
"The base url that links with this type must respect (e.g. `%(url)s`)"
|
||||
)
|
||||
% {"url": "https://www.instagram.com"},
|
||||
)
|
||||
icon = models.CharField(
|
||||
_("icon"),
|
||||
max_length=40,
|
||||
help_text=_("The fontawesome class to use (e.g. `fa-brands fa-instagram`)"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("link type")
|
||||
verbose_name_plural = _("link types")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ClubLink(models.Model):
|
||||
link_type = models.ForeignKey(
|
||||
LinkType,
|
||||
verbose_name=_("link type"),
|
||||
on_delete=models.CASCADE,
|
||||
related_name="links",
|
||||
)
|
||||
name = models.CharField(_("name"), max_length=40, blank=True)
|
||||
url = models.URLField(_("link url"))
|
||||
club = models.ForeignKey(
|
||||
Club, verbose_name=_("club"), on_delete=models.CASCADE, related_name="links"
|
||||
)
|
||||
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
||||
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("club link")
|
||||
verbose_name_plural = _("club links")
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["club", "url"],
|
||||
name="club_clublink_unique_club_url",
|
||||
violation_error_message=_("Duplicated url"),
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
|
||||
def save(self, **kwargs):
|
||||
if not self.name:
|
||||
self.name = self.link_type.name
|
||||
return super().save(**kwargs)
|
||||
|
||||
def clean(self):
|
||||
if not self.url.startswith(self.link_type.url_base):
|
||||
raise ValidationError(
|
||||
_("This link doesn't match with the url base of its type.")
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@ from typing import Annotated
|
||||
|
||||
from django.db.models import Q
|
||||
from ninja import FilterLookup, FilterSchema, ModelSchema
|
||||
from pydantic import HttpUrl
|
||||
|
||||
from club.models import Club, ClubRole, Membership
|
||||
from core.schemas import NonEmptyStr, SimpleUserSchema
|
||||
@@ -63,11 +62,6 @@ class ClubSchema(ModelSchema):
|
||||
fields = ["id", "name", "logo", "is_active", "short_description", "address"]
|
||||
|
||||
members: list[ClubMemberSchema]
|
||||
links: list[HttpUrl]
|
||||
|
||||
@staticmethod
|
||||
def resolve_links(obj: Club):
|
||||
return [link.url for link in obj.links.all()]
|
||||
|
||||
|
||||
class UserMembershipSchema(ModelSchema):
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#club-detail {
|
||||
img.club-logo {
|
||||
display: block;
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
}
|
||||
#club-attributes {
|
||||
ul {
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .75rem;
|
||||
|
||||
li i {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.has-links) {
|
||||
#club-attributes {
|
||||
float: right;
|
||||
margin: 1em 0 1em 2em;
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
margin-left: 1em;
|
||||
}
|
||||
@media screen and (max-width: 400px) {
|
||||
float: unset;
|
||||
img.club-logo {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-links {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 2em;
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
#club-attributes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
min-width: 200px;
|
||||
@media screen and (max-width: 650px) {
|
||||
margin-top: 1em;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
img.club-logo {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,43 +21,15 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_css %}
|
||||
<link rel="stylesheet" href="{{ static("club/detail.scss") }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h3>{{ club.name }}</h3>
|
||||
<div id="club-detail" {% if links %}class="has-links"{% endif %}>
|
||||
<div id="club-attributes">
|
||||
{% if club.logo %}
|
||||
<img
|
||||
class="club-logo"
|
||||
src="{{ club.logo.url }}"
|
||||
alt="{{ club.name }}"
|
||||
width="{{ club.logo.width }}"
|
||||
height="{{ club.logo.height }}"
|
||||
>
|
||||
{% endif %}
|
||||
{% if links %}
|
||||
<div id="club-links">
|
||||
<h4>{% trans %}Links{% endtrans %}</h4>
|
||||
<ul>
|
||||
{% for link in links %}
|
||||
<li>
|
||||
<a href="{{ link.url }}" rel="noopener external" target="_blank">
|
||||
<i class="{{ link.link_type.icon }} fa-xl"></i>{{ link.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="club-page">
|
||||
{% if page_revision %}
|
||||
{{ page_revision|markdown }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="club_detail">
|
||||
{% if club.logo %}
|
||||
<div class="club_logo"><img src="{{ club.logo.url }}" alt="{{ club.name }}"></div>
|
||||
{% endif %}
|
||||
<h3>{{ club.name }}</h3>
|
||||
{% if page_revision %}
|
||||
{{ page_revision|markdown }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -69,18 +69,6 @@
|
||||
{{ club.name }} {% if not club.is_active %}({% trans %}inactive{% endtrans %}){% endif %}
|
||||
</h4>
|
||||
</a>
|
||||
{% set links = club.links.all() %}
|
||||
{% if links %}
|
||||
<br>
|
||||
<div class="row gap-2x">
|
||||
{% for link in club.links.all() %}
|
||||
<a href="{{ link.url }}" rel="noopener external" target="_blank">
|
||||
<i class="{{ link.link_type.icon }} fa-xl"></i>
|
||||
<strong>{{ link.name }}</strong>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ club.short_description|markdown }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,63 +1,9 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
|
||||
{% block additional_js %}
|
||||
<script type="module" src="{{ static("bundled/core/dynamic-formset-index.ts") }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% trans name=object %}Edit {{ name }}{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% macro link_form(form) %}
|
||||
<fieldset
|
||||
{# set url in x-init rather than in x-data,
|
||||
in order to trigger the $watch on initial load #}
|
||||
x-data="{ url: '', linkType: { icon: '', id: 0 } }"
|
||||
x-init="() => {
|
||||
$watch('url', (u) => linkType = linkTypes.find((t) => u.startsWith(t.url)));
|
||||
url = '{{ form.url.value() or "" }}';
|
||||
}"
|
||||
>
|
||||
{{ form.non_field_errors() }}
|
||||
<div class="form-group row gap-2x">
|
||||
<div>
|
||||
{{ form.url.label_tag() }}
|
||||
{{ form.url.errors }}
|
||||
<span>
|
||||
{# we change the icon when the user change it and leave the input,
|
||||
or when it is pasted from the clipboard #}
|
||||
{{ form.url|add_attr("x-model.change=url,@paste.prevent=url = $event.clipboardData.getData('text')") }}
|
||||
<i
|
||||
:class="linkType.icon || 'fa fa-link'"
|
||||
tooltip="{% trans %}This icon will change according to the given url.{% endtrans %}"
|
||||
></i>
|
||||
</span>
|
||||
</div>
|
||||
<div>{{ form.name.as_field_group() }}</div>
|
||||
</div>
|
||||
{%- if form.DELETE -%}
|
||||
<div class="form-group row gap">
|
||||
{{ form.DELETE.as_field_group() }}
|
||||
</div>
|
||||
{%- else -%}
|
||||
<br>
|
||||
<button
|
||||
class="btn btn-grey"
|
||||
@click.prevent="removeForm($event.target.closest('fieldset'))"
|
||||
>
|
||||
<i class="fa fa-minus"></i> {% trans %}Remove link{% endtrans %}
|
||||
</button>
|
||||
{%- endif -%}
|
||||
{{ form.link_type|add_attr(":value=linkType.id") }}
|
||||
{%- for field in form.hidden_fields() -%}
|
||||
{%- if field != form.link_type -%}
|
||||
{{ field }}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</fieldset>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans name=object %}Edit {{ name }}{% endtrans %}</h2>
|
||||
|
||||
@@ -71,7 +17,7 @@
|
||||
and explicitly separate them from the non-admin ones,
|
||||
with some help text.
|
||||
Non-admin users will only see the regular form fields,
|
||||
so they don't need those explanations #}
|
||||
so they don't need thoses explanations #}
|
||||
<h3>{% trans %}Club properties{% endtrans %}</h3>
|
||||
<p class="helptext">
|
||||
{% trans trimmed %}
|
||||
@@ -79,7 +25,7 @@
|
||||
Only admin users can see and edit them.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<fieldset class="margin-bottom">
|
||||
<fieldset class="required margin-bottom">
|
||||
{% for field_name in form.admin_fields %}
|
||||
{% set field = form[field_name] %}
|
||||
<div class="form-group">
|
||||
@@ -90,13 +36,11 @@
|
||||
{# Remove the the admin fields from the form.
|
||||
The remaining non-admin fields will be rendered
|
||||
at once with a simple {{ form.as_p() }} #}
|
||||
{% do form.fields.pop(field_name) %}
|
||||
{% set _ = form.fields.pop(field_name) %}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans %}Club informations{% endtrans %}</h3>
|
||||
{% if form.admin_fields %}
|
||||
<h3>{% trans %}Club informations{% endtrans %}</h3>
|
||||
<p class="helptext">
|
||||
{% trans trimmed %}
|
||||
The following form fields are linked to the basic description of a club.
|
||||
@@ -104,45 +48,7 @@
|
||||
{% endtrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<fieldset class="margin-bottom">
|
||||
{{ form.as_p() }}
|
||||
</fieldset>
|
||||
|
||||
<h3>{% trans %}Club links{% endtrans %}</h3>
|
||||
<div x-data="dynamicFormSet({ prefix: '{{ form.link_formset.prefix }}' })" class="margin-bottom">
|
||||
{{ form.link_formset.management_form }}
|
||||
<div x-ref="formContainer">
|
||||
{%- for f in form.link_formset.forms -%}
|
||||
{{ link_form(f) }}
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
<template x-ref="formTemplate">
|
||||
{{ link_form(form.link_formset.empty_form) }}
|
||||
</template>
|
||||
<p>
|
||||
<i>{% trans trimmed %}
|
||||
Note: if the icon of one of your links doesn't exist yet,
|
||||
you can ask the info team to add it.
|
||||
{% endtrans %}</i>
|
||||
</p>
|
||||
<br>
|
||||
<button @click.prevent="addForm()" class="btn btn-grey">
|
||||
<i class="fa fa-plus"></i>{% trans %}Add link{% endtrans %}
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<button type="submit" class="btn btn-blue">
|
||||
<i class="fa fa-check"></i>{% trans %}Save{% endtrans %}
|
||||
</button>
|
||||
{{ form.as_p() }}
|
||||
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
const linkTypes = [
|
||||
{%- for t in link_types -%}
|
||||
{ id: {{ t.id }}, url: '{{ t.url_base }}', icon: '{{ t.icon }}' },
|
||||
{%- endfor -%}
|
||||
];
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -88,8 +88,8 @@ class TestFetchClub:
|
||||
def test_fetch_club_nb_queries(self, client: Client, club: Club):
|
||||
user = subscriber_user.make()
|
||||
client.force_login(user)
|
||||
with assertNumQueries(7):
|
||||
with assertNumQueries(6):
|
||||
# - 4 queries for authentication
|
||||
# - 3 queries for the actual data
|
||||
# - 2 queries for the actual data
|
||||
res = client.get(reverse("api:fetch_club", kwargs={"club_id": club.id}))
|
||||
assert res.status_code == 200
|
||||
|
||||
@@ -21,13 +21,7 @@ def test_club_board_member_cannot_edit_club_properties(client: Client):
|
||||
client.force_login(user)
|
||||
res = client.post(
|
||||
reverse("club:club_edit", kwargs={"club_id": club.id}),
|
||||
{
|
||||
"name": "new name",
|
||||
"is_active": False,
|
||||
"address": "new address",
|
||||
"link-TOTAL_FORMS": 0,
|
||||
"link-INITIAL_FORMS": 0,
|
||||
},
|
||||
{"name": "new name", "is_active": False, "address": "new address"},
|
||||
)
|
||||
# The request should success,
|
||||
# but admin-only fields shouldn't be taken into account
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_page_display_on_club_main_page(client: Client):
|
||||
|
||||
assert res.status_code == 200
|
||||
soup = BeautifulSoup(res.text, "lxml")
|
||||
detail_html = soup.find(id="club-page").find(class_="markdown")
|
||||
detail_html = soup.find(id="club_detail").find(class_="markdown")
|
||||
assertHTMLEqual(detail_html.decode_contents(), markdown(content))
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_club_main_page_without_content(client: Client):
|
||||
|
||||
assert res.status_code == 200
|
||||
soup = BeautifulSoup(res.text, "lxml")
|
||||
detail_html = soup.find(id="club-page")
|
||||
detail_html = soup.find(id="club_detail")
|
||||
assert detail_html.find_all("markdown") == []
|
||||
|
||||
|
||||
|
||||
+9
-21
@@ -36,8 +36,7 @@ from django.contrib.auth.mixins import (
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
|
||||
from django.core.paginator import InvalidPage, Paginator
|
||||
from django.db.models import F, Prefetch, Q, Sum
|
||||
from django.db.models.functions import Length
|
||||
from django.db.models import F, Q, Sum
|
||||
from django.http import Http404, StreamingHttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
@@ -48,7 +47,12 @@ from django.utils.translation import gettext
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DetailView, ListView, View
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormMixin, UpdateView
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
FormMixin,
|
||||
UpdateView,
|
||||
)
|
||||
|
||||
from club.forms import (
|
||||
ClubAddMemberForm,
|
||||
@@ -62,15 +66,7 @@ from club.forms import (
|
||||
MailingForm,
|
||||
SellingsForm,
|
||||
)
|
||||
from club.models import (
|
||||
Club,
|
||||
ClubLink,
|
||||
ClubRole,
|
||||
LinkType,
|
||||
Mailing,
|
||||
MailingSubscription,
|
||||
Membership,
|
||||
)
|
||||
from club.models import Club, ClubRole, Mailing, MailingSubscription, Membership
|
||||
from com.models import Poster
|
||||
from com.views import (
|
||||
PosterCreateBaseView,
|
||||
@@ -214,9 +210,7 @@ class ClubListView(AllowFragment, FormMixin, ListView):
|
||||
|
||||
template_name = "club/club_list.jinja"
|
||||
form_class = ClubSearchForm
|
||||
queryset = Club.objects.prefetch_related(
|
||||
Prefetch("links", queryset=ClubLink.objects.select_related("link_type"))
|
||||
).order_by("name")
|
||||
queryset = Club.objects.order_by("name")
|
||||
paginate_by = 20
|
||||
|
||||
def get_form_kwargs(self):
|
||||
@@ -255,7 +249,6 @@ class ClubView(ClubTabsMixin, DetailView):
|
||||
.values_list("content", flat=True)
|
||||
.first()
|
||||
)
|
||||
kwargs["links"] = list(self.object.links.select_related("link_type").all())
|
||||
return kwargs
|
||||
|
||||
|
||||
@@ -696,11 +689,6 @@ class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView):
|
||||
return ClubAdminEditForm
|
||||
return ClubEditForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(**kwargs) | {
|
||||
"link_types": list(LinkType.objects.order_by(Length("url_base").desc()))
|
||||
}
|
||||
|
||||
|
||||
class ClubCreateView(PermissionRequiredMixin, CreateView):
|
||||
"""Create a club (for the Sith admin)."""
|
||||
|
||||
@@ -16,13 +16,9 @@
|
||||
#right_column {
|
||||
flex: 20%;
|
||||
margin: 3.2px;
|
||||
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
@media screen and (min-width: 800px) {
|
||||
max-width: 20%;
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
#left_column {
|
||||
@@ -50,7 +46,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
@media screen and (max-width: $small-devices) {
|
||||
#left_column,
|
||||
#right_column {
|
||||
flex: 100%;
|
||||
@@ -80,8 +76,8 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: white;
|
||||
font-size: 70%;
|
||||
margin-bottom: 1em;
|
||||
font-size: 85%;
|
||||
|
||||
#links_content {
|
||||
overflow: auto;
|
||||
@@ -100,10 +96,23 @@
|
||||
li {
|
||||
margin: 10px;
|
||||
|
||||
.fa-facebook {
|
||||
color: $faceblue;
|
||||
}
|
||||
|
||||
.fa-discord {
|
||||
color: $discordblurple;
|
||||
}
|
||||
|
||||
.fa-square-instagram::before {
|
||||
background: $instagradient;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
i {
|
||||
width: 25px;
|
||||
text-align: center;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,13 +122,12 @@
|
||||
#birthdays_content {
|
||||
box-shadow: $shadow-color 1px 1px 1px;
|
||||
padding: 1em;
|
||||
|
||||
ul.birthdays_year {
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
font-weight: bold;
|
||||
|
||||
> li {
|
||||
>li {
|
||||
padding: 0.5em;
|
||||
|
||||
&:nth-child(even) {
|
||||
|
||||
@@ -36,7 +36,7 @@ from django.utils import timezone
|
||||
from django.utils.timezone import localdate
|
||||
from PIL import Image
|
||||
|
||||
from club.models import Club, ClubLink, ClubRole, LinkType, Membership
|
||||
from club.models import Club, ClubRole, Membership
|
||||
from com.ics_calendar import IcsCalendar
|
||||
from com.models import News, NewsDate, Sith, Weekmail
|
||||
from core.models import BanGroup, Group, Page, PageRev, SithFile, User
|
||||
@@ -830,54 +830,6 @@ class Command(BaseCommand):
|
||||
):
|
||||
roles.append(ClubRole(club=club, order=i, name=role))
|
||||
ClubRole.objects.bulk_create(roles)
|
||||
insta, fb, discord, _ = LinkType.objects.bulk_create(
|
||||
[
|
||||
LinkType(
|
||||
name="instagram",
|
||||
icon="fa-brands fa-square-instagram",
|
||||
url_base="https://www.instagram.com",
|
||||
),
|
||||
LinkType(
|
||||
name="facebook",
|
||||
icon="fa-brands fa-facebook",
|
||||
url_base="https://www.facebook.com",
|
||||
),
|
||||
LinkType(
|
||||
name="discord",
|
||||
icon="fa-brands fa-discord",
|
||||
url_base="https://discord.gg",
|
||||
),
|
||||
LinkType(name="generic", icon="fa fa-link", url_base=""),
|
||||
]
|
||||
)
|
||||
ClubLink.objects.bulk_create(
|
||||
[
|
||||
ClubLink(
|
||||
name="insta AE",
|
||||
url="https://www.instagram.com/ae_utbm/",
|
||||
club=ae,
|
||||
link_type=insta,
|
||||
),
|
||||
ClubLink(
|
||||
name="insta activités AE",
|
||||
url="https://www.instagram.com/activites_ae/",
|
||||
club=ae,
|
||||
link_type=insta,
|
||||
),
|
||||
ClubLink(
|
||||
name="facebook AE",
|
||||
url="https://www.facebook.com/ae_utbm",
|
||||
club=ae,
|
||||
link_type=fb,
|
||||
),
|
||||
ClubLink(
|
||||
name="Discord",
|
||||
url="https://discord.gg/QvTm3XJrHR",
|
||||
club=ae,
|
||||
link_type=discord,
|
||||
),
|
||||
]
|
||||
)
|
||||
return PopulatedClubs(ae=ae, troll=troll, pdf=pdf, refound=refound)
|
||||
|
||||
def _create_groups(self) -> PopulatedGroups:
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Alpine } from "alpinejs";
|
||||
import { limitedChoices } from "#core:alpine/limited-choices";
|
||||
import {
|
||||
type NotificationPlugin,
|
||||
notificationsPlugin as notifications,
|
||||
alpinePlugin as notificationPlugin,
|
||||
} from "#core:utils/notifications";
|
||||
|
||||
declare module "alpinejs" {
|
||||
@@ -11,8 +11,8 @@ declare module "alpinejs" {
|
||||
$notifications: NotificationPlugin;
|
||||
}
|
||||
}
|
||||
|
||||
Alpine.plugin([sort, limitedChoices, notifications]);
|
||||
Alpine.plugin([sort, limitedChoices]);
|
||||
Alpine.magic("notifications", notificationPlugin);
|
||||
// biome-ignore lint/style/useNamingConvention: it's how it's named
|
||||
Object.assign(window, { Alpine });
|
||||
|
||||
|
||||
@@ -7,30 +7,11 @@ export enum NotificationLevel {
|
||||
}
|
||||
|
||||
export interface NotificationPlugin {
|
||||
/**
|
||||
* Add an error message to the notifications.
|
||||
*/
|
||||
error: (message: string) => void;
|
||||
/**
|
||||
* Add a warning message to the notifications
|
||||
*/
|
||||
warning: (message: string) => void;
|
||||
/**
|
||||
* Add a success message to the notifications
|
||||
*/
|
||||
success: (message: string) => void;
|
||||
/**
|
||||
* Remove all notifications displayed on the page.
|
||||
*/
|
||||
clear: () => void;
|
||||
/**
|
||||
* Add multiple notifications at once.
|
||||
* The added notifications can have different notification levels.
|
||||
*/
|
||||
addMany: (notifs: Notification[]) => void;
|
||||
/**
|
||||
* Return all notifications displayed on the page.
|
||||
*/
|
||||
getAll: () => Notification[];
|
||||
}
|
||||
|
||||
@@ -44,21 +25,27 @@ Alpine.store("notifications", [] as Notification[]);
|
||||
function createNotification(message: string, level: NotificationLevel) {
|
||||
(Alpine.store("notifications") as Notification[]).push({ text: message, tag: level });
|
||||
}
|
||||
function createManyNotifications(notifs: Notification[]) {
|
||||
for (const notif of notifs) {
|
||||
createNotification(notif.text, notif.tag);
|
||||
}
|
||||
|
||||
function deleteNotifications() {
|
||||
Alpine.store("notifications", []);
|
||||
}
|
||||
|
||||
export const notifications: NotificationPlugin = {
|
||||
error: (message: string) => createNotification(message, NotificationLevel.Error),
|
||||
warning: (message: string) => createNotification(message, NotificationLevel.Warning),
|
||||
success: (message: string) => createNotification(message, NotificationLevel.Success),
|
||||
clear: () => Alpine.store("notifications", []),
|
||||
addMany: (notifs: Notification[]) => createManyNotifications(notifs),
|
||||
getAll: () => Alpine.store("notifications") as Notification[],
|
||||
};
|
||||
|
||||
export function notificationsPlugin(GlobalAlpine: Alpine) {
|
||||
GlobalAlpine.magic("notifications", () => ({ ...notifications }));
|
||||
function getNotifications() {
|
||||
return Alpine.store("notifications") as Notification[];
|
||||
}
|
||||
|
||||
export function alpinePlugin(): NotificationPlugin {
|
||||
return {
|
||||
error: (message: string) => createNotification(message, NotificationLevel.Error),
|
||||
warning: (message: string) =>
|
||||
createNotification(message, NotificationLevel.Warning),
|
||||
success: (message: string) =>
|
||||
createNotification(message, NotificationLevel.Success),
|
||||
clear: () => deleteNotifications(),
|
||||
addMany: (notifs: Notification[]) =>
|
||||
notifs.forEach((n) => {
|
||||
createNotification(n.text, n.tag);
|
||||
}),
|
||||
getAll: () => getNotifications(),
|
||||
};
|
||||
}
|
||||
|
||||
+13
-22
@@ -398,28 +398,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* Fontawesome icons */
|
||||
.fa-brands, .fa-link {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.fa-facebook {
|
||||
color: $faceblue;
|
||||
}
|
||||
|
||||
.fa-discord {
|
||||
color: $discordblurple;
|
||||
}
|
||||
|
||||
.fa-square-instagram::before, .fa-instagram::before {
|
||||
background: $instagradient;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.fa-bluesky, .fa-square-bluesky {
|
||||
color: #0f73ff;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-devices) {
|
||||
@@ -771,3 +749,16 @@ textarea {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------JQuery-------------------------------*/
|
||||
#club_detail {
|
||||
.club_logo {
|
||||
float: right;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-height: 10em;
|
||||
max-width: 10em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
<div id="quick-notifications"
|
||||
x-data='{ messages: $notifications.getAll() }'
|
||||
x-init='$notifications.addMany([
|
||||
{%- for message in messages -%}
|
||||
{%- if not message.extra_tags -%}
|
||||
{ tag: "{{ message.tags }}", text: {{ message|string|tojson }} },
|
||||
{ tag: {{ message.tags|string|tojson }}, text: {{ message|string|tojson }} },
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
])'
|
||||
>
|
||||
<template x-for="(message, index) in $notifications.getAll()">
|
||||
<template x-for="(message, index) in messages">
|
||||
<div class="alert" :class="`alert-${message.tag}`" x-transition>
|
||||
<span class="alert-main" x-text="message.text"></span>
|
||||
<span class="clickable" @click="messages = messages.filter((item, i) => i !== index)">
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
<button type="button" onclick="checkbox_{{form_id}}(false);">{% trans %}Unselect All{% endtrans %}</button>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro update_notifications(messages, clear = True) %}
|
||||
{% macro update_notifications(messages, clear) %}
|
||||
{# Update notification area from new messages sent by django backend
|
||||
This is useful when performing fragment swaps to keep messages up to date
|
||||
Without this, the fragment would need to take control of the notification area and
|
||||
@@ -236,19 +236,16 @@
|
||||
messages: messages from django.contrib
|
||||
clear : optional boolean that controls if notifications should be cleared first. True is the default
|
||||
#}
|
||||
{% set clear = clear|default(true) %}
|
||||
{% if messages %}
|
||||
<div x-init='() => {
|
||||
{%- if clear -%}
|
||||
$notifications.clear();
|
||||
{%- endif -%}
|
||||
$notifications.addMany([
|
||||
{%- for message in messages -%}
|
||||
{%- if not message.extra_tags -%}
|
||||
{ tag: "{{ message.tags }}", text: {{ message|string|tojson }} },
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
])
|
||||
}'></div>
|
||||
<div x-init="() => {
|
||||
{% if clear %}
|
||||
$notifications.clear()
|
||||
{% endif %}
|
||||
{% for message in messages %}
|
||||
$notifications.{{ message.tags }}('{{ message }}')
|
||||
{% endfor %}
|
||||
}"></div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ def add_attr(field: BoundField, attr: str):
|
||||
if "=" not in d:
|
||||
attrs["class"] = d
|
||||
else:
|
||||
key, val = d.split("=", maxsplit=1)
|
||||
key, val = d.split("=")
|
||||
attrs[key] = val
|
||||
|
||||
return field.as_widget(attrs=attrs)
|
||||
|
||||
@@ -54,7 +54,7 @@ class FragmentRenderer(Protocol):
|
||||
) -> SafeString: ...
|
||||
|
||||
|
||||
class FragmentMixin(TemplateResponseMixin, AllowFragment, ContextMixin):
|
||||
class FragmentMixin(TemplateResponseMixin, ContextMixin):
|
||||
"""Make a view buildable as a fragment that can be embedded in a template.
|
||||
|
||||
Most fragments are used in two different ways :
|
||||
|
||||
@@ -409,6 +409,7 @@ class ProductForm(forms.ModelForm):
|
||||
"club",
|
||||
"limit_age",
|
||||
"tray",
|
||||
"clic_limit",
|
||||
"archived",
|
||||
]
|
||||
help_texts = {
|
||||
|
||||
@@ -32,9 +32,8 @@ class Migration(migrations.Migration):
|
||||
(
|
||||
"result",
|
||||
models.OneToOneField(
|
||||
help_text="The product got with the formula.",
|
||||
help_text="The formula product.",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="formula",
|
||||
to="counter.product",
|
||||
verbose_name="result product",
|
||||
),
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.2.13 on 2026-05-13 11:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("counter", "0039_price")]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(model_name="product", name="buying_groups"),
|
||||
migrations.AddField(
|
||||
model_name="product",
|
||||
name="clic_limit",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
blank=True,
|
||||
help_text=(
|
||||
"If a limit is set, the product won't be purchasable "
|
||||
"anymore once the latter is reached."
|
||||
),
|
||||
null=True,
|
||||
verbose_name="clic limit",
|
||||
),
|
||||
),
|
||||
]
|
||||
+47
-15
@@ -22,7 +22,7 @@ import string
|
||||
from datetime import date, datetime, timedelta
|
||||
from datetime import timezone as tz
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING, Literal, Self
|
||||
from typing import Literal, Self
|
||||
|
||||
from dict2xml import dict2xml
|
||||
from django.conf import settings
|
||||
@@ -34,6 +34,7 @@ from django.forms import ValidationError
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
from django_countries.fields import CountryField
|
||||
@@ -47,9 +48,6 @@ from core.utils import get_start_of_semester
|
||||
from counter.fields import CurrencyField
|
||||
from subscription.models import Subscription
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def get_eboutic() -> Counter:
|
||||
return Counter.objects.filter(type="EBOUTIC").order_by("id").first()
|
||||
@@ -353,6 +351,38 @@ class ProductType(OrderedModel):
|
||||
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
|
||||
|
||||
class ProductQuerySet(models.QuerySet):
|
||||
def under_clic_limit(self) -> Self:
|
||||
"""Filter product which clic limit isn't reached yet.
|
||||
|
||||
The clic limit is reached when the amount of sales
|
||||
and of items in a basket for less than 15 minutes
|
||||
is greater or equal than `Product.clic_limit`.
|
||||
"""
|
||||
# import here to avoid circular import
|
||||
from eboutic.models import BasketItem
|
||||
|
||||
nb_click_subquery = Subquery(
|
||||
Selling.objects.filter(product_id=OuterRef("id"))
|
||||
.values("product_id")
|
||||
.annotate(res=Sum("quantity", default=0))
|
||||
.values("res")[:1]
|
||||
)
|
||||
nb_basket_items_subquery = Subquery(
|
||||
BasketItem.objects.filter(
|
||||
product_id=OuterRef("id"),
|
||||
basket__date__gt=now() - settings.SITH_EBOUTIC_BASKET_TIMEOUT,
|
||||
)
|
||||
.values("product_id")
|
||||
.annotate(res=Sum("quantity"))
|
||||
.values("res")[:1]
|
||||
)
|
||||
return self.annotate(
|
||||
clicked=Coalesce(nb_click_subquery, 0),
|
||||
reserved=Coalesce(nb_basket_items_subquery, 0),
|
||||
).filter(Q(clic_limit=None) | Q(clic_limit__gt=(F("clicked") + F("reserved"))))
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
"""A product, with all its related information."""
|
||||
|
||||
@@ -370,8 +400,7 @@ class Product(models.Model):
|
||||
)
|
||||
code = models.CharField(_("code"), max_length=16, blank=True)
|
||||
purchase_price = CurrencyField(
|
||||
_("purchase price"),
|
||||
help_text=_("Initial cost of purchasing the product"),
|
||||
_("purchase price"), help_text=_("Initial cost of purchasing the product")
|
||||
)
|
||||
icon = ResizedImageField(
|
||||
height=70,
|
||||
@@ -388,13 +417,21 @@ class Product(models.Model):
|
||||
tray = models.BooleanField(
|
||||
_("tray price"), help_text=_("Buy five, get the sixth free"), default=False
|
||||
)
|
||||
buying_groups = models.ManyToManyField(
|
||||
Group, related_name="products", verbose_name=_("buying groups"), blank=True
|
||||
clic_limit = models.PositiveSmallIntegerField(
|
||||
_("clic limit"),
|
||||
help_text=_(
|
||||
"If a limit is set, the product won't be purchasable "
|
||||
"anymore on the eboutic once the latter is reached."
|
||||
),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
archived = models.BooleanField(_("archived"), default=False)
|
||||
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
||||
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
|
||||
|
||||
objects = ProductQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("product")
|
||||
|
||||
@@ -733,10 +770,8 @@ class Counter(models.Model):
|
||||
# but they share the same primary key
|
||||
return self.type == "BAR" and any(b.pk == customer.pk for b in self.barmen_list)
|
||||
|
||||
def get_prices_for(
|
||||
self, customer: Customer, *, order_by: Sequence[str] | None = None
|
||||
) -> list[Price]:
|
||||
qs = (
|
||||
def get_prices_for(self, customer: Customer) -> PriceQuerySet:
|
||||
return (
|
||||
Price.objects.filter(
|
||||
product__counters=self, product__product_type__isnull=False
|
||||
)
|
||||
@@ -744,9 +779,6 @@ class Counter(models.Model):
|
||||
.select_related("product", "product__product_type")
|
||||
.prefetch_related("groups")
|
||||
)
|
||||
if order_by:
|
||||
qs = qs.order_by(*order_by)
|
||||
return list(qs)
|
||||
|
||||
|
||||
class CounterSellers(models.Model):
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset><div>{{ form.clic_limit.as_field_group() }}</div></fieldset>
|
||||
<fieldset><div>{{ form.counters.as_field_group() }}</div></fieldset>
|
||||
|
||||
<h3 class="margin-bottom">{% trans %}Prices{% endtrans %}</h3>
|
||||
|
||||
@@ -596,7 +596,7 @@ class TestCounterClick(TestFullClickBase):
|
||||
product=iter(_product_recipe.make(archived=False, _quantity=2)),
|
||||
groups=[group],
|
||||
)
|
||||
customer_prices = counter.get_prices_for(customer)
|
||||
customer_prices = list(counter.get_prices_for(customer))
|
||||
assert unarchived_prices == customer_prices
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import itertools
|
||||
from datetime import timedelta
|
||||
from io import BytesIO
|
||||
from typing import Callable
|
||||
from uuid import uuid4
|
||||
@@ -8,6 +10,7 @@ from django.core.cache import cache
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from model_bakery import baker
|
||||
from model_bakery.recipe import Recipe
|
||||
from PIL import Image
|
||||
@@ -16,9 +19,10 @@ from pytest_django.asserts import assertNumQueries, assertRedirects
|
||||
from club.models import Club
|
||||
from core.baker_recipes import board_user, subscriber_user
|
||||
from core.models import Group, User
|
||||
from counter.baker_recipes import product_recipe
|
||||
from counter.baker_recipes import product_recipe, sale_recipe
|
||||
from counter.forms import ProductForm, ProductPriceFormSet
|
||||
from counter.models import Price, Product, ProductType
|
||||
from counter.models import Price, Product, ProductType, Selling
|
||||
from eboutic.models import Basket, BasketItem
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -222,3 +226,57 @@ def test_price_for_user():
|
||||
assert list(qs.for_user(users[0])) == [prices[0], prices[1], prices[4]]
|
||||
assert list(qs.for_user(users[1])) == [prices[0], prices[4]]
|
||||
assert list(qs.for_user(users[2])) == [prices[0], prices[3]]
|
||||
|
||||
|
||||
class TestProductClicLimit(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.products = product_recipe.make(
|
||||
clic_limit=itertools.chain([5, 10, 15], itertools.repeat(None)),
|
||||
_quantity=6,
|
||||
_bulk_create=True,
|
||||
)
|
||||
cls.qs = Product.objects.filter(id__in=[p.id for p in cls.products])
|
||||
|
||||
def test_no_sales_or_basket(self):
|
||||
"""Test that it works if no sales has been made yet"""
|
||||
assert list(self.qs.under_clic_limit()) == self.products
|
||||
|
||||
def test_with_sales(self):
|
||||
"""Test that it works when there are existing sales"""
|
||||
sales = sale_recipe.make(
|
||||
product=itertools.cycle(self.products),
|
||||
_quantity=len(self.products) * 5,
|
||||
_bulk_create=True,
|
||||
)
|
||||
Selling.objects.filter(id__in=[s.id for s in sales]).update(quantity=2)
|
||||
assert list(self.qs.under_clic_limit()) == self.products[2:]
|
||||
|
||||
def test_with_sales_and_basket(self):
|
||||
"""Test that it works when there are existing sales and basket items."""
|
||||
sales = sale_recipe.make(
|
||||
product=itertools.cycle(self.products),
|
||||
_quantity=len(self.products) * 5,
|
||||
_bulk_create=True,
|
||||
)
|
||||
Selling.objects.filter(id__in=[s.id for s in sales]).update(quantity=1)
|
||||
basket = baker.make(
|
||||
Basket, date=now() - settings.SITH_EBOUTIC_BASKET_TIMEOUT / 2
|
||||
)
|
||||
items = baker.make(
|
||||
BasketItem,
|
||||
product=itertools.cycle(self.products),
|
||||
basket=basket,
|
||||
_quantity=len(self.products) * 5,
|
||||
)
|
||||
BasketItem.objects.filter(id__in=[i.id for i in items]).update(quantity=1)
|
||||
assert list(self.qs.under_clic_limit()) == self.products[2:]
|
||||
|
||||
# expired basket items shouldn't be accounted when computing clic limit
|
||||
item = BasketItem.objects.filter(product=self.products[1])[0]
|
||||
item.basket = baker.make(
|
||||
Basket,
|
||||
date=now() - settings.SITH_EBOUTIC_BASKET_TIMEOUT - timedelta(minutes=1),
|
||||
)
|
||||
item.save()
|
||||
assert list(self.qs.under_clic_limit()) == self.products[1:]
|
||||
|
||||
@@ -103,7 +103,7 @@ class CounterClick(
|
||||
):
|
||||
return redirect(obj) # Redirect to counter
|
||||
|
||||
self.prices = obj.get_prices_for(self.customer)
|
||||
self.prices = list(obj.get_prices_for(self.customer))
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
+10
-1
@@ -1,3 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
from ninja import Status
|
||||
from ninja_extra import ControllerBase, api_controller, route
|
||||
from ninja_extra.exceptions import NotFound
|
||||
|
||||
@@ -8,13 +11,19 @@ from eboutic.models import Basket
|
||||
|
||||
@api_controller("/etransaction", permissions=[CanView])
|
||||
class EtransactionInfoController(ControllerBase):
|
||||
@route.get("/data/{basket_id}", url_name="etransaction_data")
|
||||
@route.get(
|
||||
"/data/{basket_id}",
|
||||
url_name="etransaction_data",
|
||||
response={200: dict[str, Any], 410: str},
|
||||
)
|
||||
def fetch_etransaction_data(self, basket_id: int):
|
||||
"""Generate the data to pay an eboutic command with paybox.
|
||||
|
||||
The data is generated with the basket that is used by the current session.
|
||||
"""
|
||||
basket: Basket = self.get_object_or_exception(Basket, pk=basket_id)
|
||||
if basket.is_expired:
|
||||
return Status(410, "This basket is expired.")
|
||||
try:
|
||||
return dict(basket.get_e_transaction_data())
|
||||
except BillingInfo.DoesNotExist as e:
|
||||
|
||||
@@ -24,6 +24,7 @@ from django.conf import settings
|
||||
from django.db import DataError, models
|
||||
from django.db.models import F, OuterRef, Subquery, Sum
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.models import User
|
||||
@@ -95,6 +96,10 @@ class Basket(models.Model):
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def is_expired(self) -> bool:
|
||||
return (self.date + settings.SITH_EBOUTIC_BASKET_TIMEOUT) <= now()
|
||||
|
||||
def generate_sales(
|
||||
self, counter, seller: User, payment_method: Selling.PaymentMethod
|
||||
):
|
||||
@@ -133,9 +138,20 @@ class Basket(models.Model):
|
||||
]
|
||||
|
||||
def get_e_transaction_data(self) -> list[tuple[str, str]]:
|
||||
"""Get data for etransaction payment.
|
||||
|
||||
Raises:
|
||||
Customer.DoesNotExist: if the user linked to this basket
|
||||
has no customer account
|
||||
BillingInfo.DoesNotExist: if the user linked to this basket has no
|
||||
billing infos, or incorrect billing infos.
|
||||
ValueError: if this is called on a basket which payment delay is expired.
|
||||
"""
|
||||
user = self.user
|
||||
if not hasattr(user, "customer"):
|
||||
raise Customer.DoesNotExist
|
||||
if self.is_expired:
|
||||
raise ValueError("This method cannot be called on an expired basket.")
|
||||
customer = user.customer
|
||||
if (
|
||||
not hasattr(user.customer, "billing_infos")
|
||||
@@ -155,6 +171,10 @@ class Basket(models.Model):
|
||||
("PBX_IDENTIFIANT", settings.SITH_EBOUTIC_PBX_IDENTIFIANT),
|
||||
("PBX_TOTAL", str(int(self.total * 100))),
|
||||
("PBX_DEVISE", "978"), # This is Euro
|
||||
(
|
||||
"PBX_DISPLAY",
|
||||
str(int(settings.SITH_EBOUTIC_ETRANSACTION_TIMEOUT.total_seconds())),
|
||||
),
|
||||
("PBX_CMD", str(self.id)),
|
||||
("PBX_PORTEUR", user.email),
|
||||
("PBX_RETOUR", "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K"),
|
||||
|
||||
@@ -1,21 +1,71 @@
|
||||
import { type Notification, NotificationLevel } from "#core:utils/notifications";
|
||||
import { etransactioninfoFetchEtransactionData } from "#openapi";
|
||||
|
||||
interface Basket {
|
||||
id: number;
|
||||
timeout: Date;
|
||||
}
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("etransaction", (initialData, basketId: number) => ({
|
||||
Alpine.data("etransaction", (initialData, basket: Basket) => ({
|
||||
data: initialData,
|
||||
isCbAvailable: Object.keys(initialData).length > 0,
|
||||
isSithAvailable: true,
|
||||
|
||||
init() {
|
||||
const now = new Date();
|
||||
const timeout = basket.timeout.getTime() - now.getTime();
|
||||
if (timeout <= 0) {
|
||||
// basket was already outdated at initial page load
|
||||
this.timeoutBasket();
|
||||
} else {
|
||||
setTimeout(() => this.timeoutBasket(), timeout);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Make this basket into a timeout state.
|
||||
* All submission inputs are disabled, and an error message is displayed.
|
||||
*/
|
||||
timeoutBasket() {
|
||||
this.isCbAvailable = false;
|
||||
this.isSithAvailable = false;
|
||||
const message = gettext("Basket expired");
|
||||
|
||||
const existingNotif: Notification | undefined = this.$notifications
|
||||
.getAll()
|
||||
.find(
|
||||
(n: Notification) =>
|
||||
n.tag === NotificationLevel.Error && n.message === message,
|
||||
);
|
||||
if (existingNotif === undefined) {
|
||||
this.$notifications.error(message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the data used for etransaction.
|
||||
*
|
||||
* Note: if this is called while the basket is expired, it will be a no-op
|
||||
*/
|
||||
async fill() {
|
||||
if (new Date() > basket.timeout) {
|
||||
// refresh etransaction data only if the basket is still valid.
|
||||
this.timeoutBasket();
|
||||
return;
|
||||
}
|
||||
this.isCbAvailable = false;
|
||||
const res = await etransactioninfoFetchEtransactionData({
|
||||
path: {
|
||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||
basket_id: basketId,
|
||||
},
|
||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||
path: { basket_id: basket.id },
|
||||
});
|
||||
if (res.response.ok) {
|
||||
this.data = res.data;
|
||||
this.isCbAvailable = true;
|
||||
} else if (res.response.status === 410) {
|
||||
// The basket is expired, so no payment method should be available at all.
|
||||
// This shouldn't happen, because we don't send the request
|
||||
// when the timeout is passed, but we are better safe than sorry
|
||||
this.timeoutBasket();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#billing-infos-fragment"
|
||||
x-show="collapsed"
|
||||
x-cloak
|
||||
>
|
||||
{% csrf_token %}
|
||||
{{ form.as_p() }}
|
||||
@@ -32,7 +33,5 @@
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
{% if is_fragment %}
|
||||
{{ update_notifications(messages) }}
|
||||
{% endif %}
|
||||
{{ update_notifications(messages) }}
|
||||
</div>
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
{% block content %}
|
||||
<h3>{% trans %}Eboutic{% endtrans %}</h3>
|
||||
|
||||
<script type="text/javascript">
|
||||
let billingInfos = {{ billing_infos|safe }};
|
||||
</script>
|
||||
|
||||
<div x-data="etransaction(billingInfos, {{ basket.id }})">
|
||||
<div x-data='etransaction(
|
||||
{{ billing_infos|tojson }},
|
||||
{ id: {{ basket.id }}, timeout: new Date('{{ basket.date + settings.SITH_EBOUTIC_BASKET_TIMEOUT }}') }
|
||||
)'>
|
||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -72,7 +71,11 @@
|
||||
x-cloak
|
||||
type="submit"
|
||||
id="bank-submit-button"
|
||||
:disabled="!isCbAvailable"
|
||||
{% if basket.is_expired %}
|
||||
disabled="disabled"
|
||||
{% else %}
|
||||
:disabled="!isCbAvailable"
|
||||
{% endif %}
|
||||
class="btn btn-blue"
|
||||
value="{% trans %}Pay with credit card{% endtrans %}"
|
||||
/>
|
||||
@@ -93,7 +96,16 @@
|
||||
{% else %}
|
||||
<form method="post" action="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}" name="sith-pay-form">
|
||||
{% csrf_token %}
|
||||
<input class="btn btn-blue" type="submit" value="{% trans %}Pay with Sith account{% endtrans %}"/>
|
||||
<input
|
||||
{% if basket.is_expired %}
|
||||
disabled="disabled"
|
||||
{% else %}
|
||||
:disabled="!isSithAvailable"
|
||||
{% endif %}
|
||||
class="btn btn-blue"
|
||||
type="submit"
|
||||
value="{% trans %}Pay with Sith account{% endtrans %}"
|
||||
/>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import urllib
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import freezegun
|
||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
from cryptography.hazmat.primitives.hashes import SHA1
|
||||
@@ -105,7 +106,7 @@ class TestPaymentSith(TestPaymentBase):
|
||||
),
|
||||
reverse("eboutic:payment_result", kwargs={"result": "success"}),
|
||||
)
|
||||
assert Basket.objects.filter(id=self.basket.id).first() is None
|
||||
assert not Basket.objects.filter(id=self.basket.id).exists()
|
||||
self.customer.customer.refresh_from_db()
|
||||
assert self.customer.customer.amount == Decimal(1)
|
||||
|
||||
@@ -139,10 +140,7 @@ class TestPaymentSith(TestPaymentBase):
|
||||
assert len(messages) == 1
|
||||
assert messages[0].level == DEFAULT_LEVELS["ERROR"]
|
||||
assert messages[0].message == "Solde insuffisant"
|
||||
|
||||
assert Basket.objects.contains(self.basket), (
|
||||
"After an unsuccessful request, the basket should be kept"
|
||||
)
|
||||
assert not Basket.objects.filter(id=self.basket.id).exists()
|
||||
|
||||
def test_refilling_in_basket(self):
|
||||
BasketItem.from_price(self.refilling.prices.first(), 1, self.basket).save()
|
||||
@@ -157,7 +155,7 @@ class TestPaymentSith(TestPaymentBase):
|
||||
response,
|
||||
reverse("eboutic:payment_result", kwargs={"result": "failure"}),
|
||||
)
|
||||
assert Basket.objects.filter(id=self.basket.id).first() is not None
|
||||
assert not Basket.objects.filter(id=self.basket.id).exists()
|
||||
messages = list(get_messages(response.wsgi_request))
|
||||
assert messages[0].level == DEFAULT_LEVELS["ERROR"]
|
||||
assert (
|
||||
@@ -167,6 +165,24 @@ class TestPaymentSith(TestPaymentBase):
|
||||
self.customer.customer.refresh_from_db()
|
||||
assert self.customer.customer.amount == initial_account_balance
|
||||
|
||||
def test_basket_expired(self):
|
||||
self.client.force_login(self.customer)
|
||||
initial_account_balance = self.customer.customer.amount
|
||||
with freezegun.freeze_time(settings.SITH_EBOUTIC_BASKET_TIMEOUT):
|
||||
response = self.client.post(
|
||||
reverse("eboutic:pay_with_sith", kwargs={"basket_id": self.basket.id})
|
||||
)
|
||||
assertRedirects(
|
||||
response,
|
||||
reverse("eboutic:payment_result", kwargs={"result": "failure"}),
|
||||
)
|
||||
messages = list(get_messages(response.wsgi_request))
|
||||
assert messages[0].level == DEFAULT_LEVELS["ERROR"]
|
||||
assert messages[0].message == "Panier expiré"
|
||||
assert not Basket.objects.filter(id=self.basket.id).exists()
|
||||
self.customer.customer.refresh_from_db()
|
||||
assert self.customer.customer.amount == initial_account_balance
|
||||
|
||||
|
||||
class TestPaymentCard(TestPaymentBase):
|
||||
def generate_bank_valid_answer(self, basket: Basket):
|
||||
|
||||
+26
-9
@@ -39,6 +39,8 @@ from django.db.utils import cached_property
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import localize
|
||||
from django.utils.timezone import localtime
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.http import require_GET
|
||||
from django.views.generic import DetailView, FormView, TemplateView, UpdateView, View
|
||||
@@ -116,9 +118,11 @@ class EbouticMainView(LoginRequiredMixin, FormView):
|
||||
|
||||
@cached_property
|
||||
def prices(self) -> list[Price]:
|
||||
return get_eboutic().get_prices_for(
|
||||
self.customer,
|
||||
order_by=["product__product_type__order", "product_id", "amount"],
|
||||
eboutic = get_eboutic()
|
||||
return list(
|
||||
eboutic.get_prices_for(self.customer)
|
||||
.filter(product__in=eboutic.products.under_clic_limit())
|
||||
.order_by("product__product_type__order", "product_id", "amount")
|
||||
)
|
||||
|
||||
@cached_property
|
||||
@@ -187,9 +191,7 @@ class BillingInfoFormFragment(
|
||||
|
||||
def get_initial(self):
|
||||
if self.object is None:
|
||||
return {
|
||||
"country": Country(code="FR"),
|
||||
}
|
||||
return {"country": Country(code="FR")}
|
||||
return {}
|
||||
|
||||
def render_fragment(self, request, **kwargs) -> SafeString:
|
||||
@@ -255,10 +257,19 @@ class EbouticCheckout(CanViewMixin, UseFragmentsMixin, DetailView):
|
||||
kwargs["customer_amount"] = None
|
||||
kwargs["billing_infos"] = {}
|
||||
|
||||
with contextlib.suppress(BillingInfo.DoesNotExist):
|
||||
kwargs["billing_infos"] = json.dumps(
|
||||
dict(self.object.get_e_transaction_data())
|
||||
if self.object.is_expired:
|
||||
messages.error(self.request, _("Basket expired"))
|
||||
else:
|
||||
timeout = self.object.date + settings.SITH_EBOUTIC_BASKET_TIMEOUT
|
||||
messages.warning(
|
||||
self.request,
|
||||
_("Basket available until %(until)s")
|
||||
% {"until": localize(localtime(timeout).time())},
|
||||
)
|
||||
with contextlib.suppress(BillingInfo.DoesNotExist):
|
||||
kwargs["billing_infos"] = json.dumps(
|
||||
dict(self.object.get_e_transaction_data())
|
||||
)
|
||||
return kwargs
|
||||
|
||||
|
||||
@@ -268,9 +279,14 @@ class EbouticPayWithSith(CanViewMixin, SingleObjectMixin, View):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
basket = self.get_object()
|
||||
if basket.is_expired:
|
||||
messages.error(self.request, _("Basket expired"))
|
||||
basket.delete()
|
||||
return redirect("eboutic:payment_result", "failure")
|
||||
refilling = settings.SITH_COUNTER_PRODUCTTYPE_REFILLING
|
||||
if basket.items.filter(product__product_type_id=refilling).exists():
|
||||
messages.error(self.request, _("You can't buy a refilling with sith money"))
|
||||
basket.delete()
|
||||
return redirect("eboutic:payment_result", "failure")
|
||||
|
||||
eboutic = get_eboutic()
|
||||
@@ -288,6 +304,7 @@ class EbouticPayWithSith(CanViewMixin, SingleObjectMixin, View):
|
||||
except DatabaseError as e:
|
||||
sentry_sdk.capture_exception(e)
|
||||
except ValidationError as e:
|
||||
basket.delete()
|
||||
messages.error(self.request, e.message)
|
||||
return redirect("eboutic:payment_result", "failure")
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-12 11:12+0200\n"
|
||||
"POT-Creation-Date: 2026-05-15 11:46+0200\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@@ -362,62 +362,6 @@ msgstr "Cet email est déjà abonné à cette mailing"
|
||||
msgid "Unregistered user"
|
||||
msgstr "Utilisateur non enregistré"
|
||||
|
||||
#: club/models.py
|
||||
#, python-format
|
||||
msgid "The base url that links with this type must respect (e.g. `%(url)s`)"
|
||||
msgstr ""
|
||||
"L'url de base que tous les liens de ce type doivent respecter (par exemple "
|
||||
"`%(url)s`)"
|
||||
|
||||
#: club/models.py counter/models.py
|
||||
msgid "icon"
|
||||
msgstr "icône"
|
||||
|
||||
#: club/models.py
|
||||
msgid "The fontawesome class to use (e.g. `fa-brands fa-instagram`)"
|
||||
msgstr ""
|
||||
"La classe fontawesome à utiliser (par exemple `fa-brands fa-instagram`)"
|
||||
|
||||
#: club/models.py
|
||||
msgid "link type"
|
||||
msgstr "type de lien"
|
||||
|
||||
#: club/models.py
|
||||
msgid "link types"
|
||||
msgstr "types de lien"
|
||||
|
||||
#: club/models.py
|
||||
msgid "link url"
|
||||
msgstr "url du lien"
|
||||
|
||||
#: club/models.py core/models.py counter/models.py
|
||||
msgid "created at"
|
||||
msgstr "créé le"
|
||||
|
||||
#: club/models.py core/models.py counter/models.py
|
||||
msgid "updated at"
|
||||
msgstr "mis à jour le"
|
||||
|
||||
#: club/models.py
|
||||
msgid "club link"
|
||||
msgstr "lien de club"
|
||||
|
||||
#: club/models.py
|
||||
msgid "club links"
|
||||
msgstr "liens de club"
|
||||
|
||||
#: club/models.py
|
||||
msgid "Duplicated url"
|
||||
msgstr "Url dupliquée"
|
||||
|
||||
#: club/models.py
|
||||
msgid "This link doesn't match with the url base of its type."
|
||||
msgstr "Ce lien ne correspond pas à l'url de base de son type."
|
||||
|
||||
#: club/templates/club/club_detail.jinja com/templates/com/news_list.jinja
|
||||
msgid "Links"
|
||||
msgstr "Liens"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "The list of all clubs existing at UTBM."
|
||||
msgstr "La liste de tous les clubs existants à l'UTBM"
|
||||
@@ -764,14 +708,6 @@ msgstr "Comptoirs : "
|
||||
msgid "Edit %(name)s"
|
||||
msgstr "Éditer %(name)s"
|
||||
|
||||
#: club/templates/club/edit_club.jinja
|
||||
msgid "This icon will change according to the given url."
|
||||
msgstr "Cette icône changera en fonction de l'url fournie"
|
||||
|
||||
#: club/templates/club/edit_club.jinja
|
||||
msgid "Remove link"
|
||||
msgstr "Retirer le lien"
|
||||
|
||||
#: club/templates/club/edit_club.jinja
|
||||
msgid "Club properties"
|
||||
msgstr "Propriétés du club"
|
||||
@@ -796,22 +732,6 @@ msgstr ""
|
||||
"Les champs de formulaire suivants sont liées à la description basique d'un "
|
||||
"club. Tous les membres du bureau du club peuvent voir et modifier ceux-ci."
|
||||
|
||||
#: club/templates/club/edit_club.jinja
|
||||
msgid "Club links"
|
||||
msgstr "Liens du club"
|
||||
|
||||
#: club/templates/club/edit_club.jinja
|
||||
msgid ""
|
||||
"Note: if the icon of one of your links doesn't exist yet, you can ask the "
|
||||
"info team to add it."
|
||||
msgstr ""
|
||||
"Note : si l'icône d'un de vos liens n'existe pas encore, vous pouvez "
|
||||
"demander au pôle info de l'ajouter."
|
||||
|
||||
#: club/templates/club/edit_club.jinja
|
||||
msgid "Add link"
|
||||
msgstr "Ajouter un lien"
|
||||
|
||||
#: club/templates/club/fragments/add_member.jinja
|
||||
msgid "Add a new member"
|
||||
msgstr "Ajouter un nouveau membre"
|
||||
@@ -1331,6 +1251,10 @@ msgstr ""
|
||||
msgid "All coming events"
|
||||
msgstr "Tous les événements à venir"
|
||||
|
||||
#: com/templates/com/news_list.jinja
|
||||
msgid "Links"
|
||||
msgstr "Liens"
|
||||
|
||||
#: com/templates/com/news_list.jinja
|
||||
msgid "Our services"
|
||||
msgstr "Nos services"
|
||||
@@ -1836,6 +1760,10 @@ msgstr "Visiteur"
|
||||
msgid "ban type"
|
||||
msgstr "type de ban"
|
||||
|
||||
#: core/models.py counter/models.py
|
||||
msgid "created at"
|
||||
msgstr "créé le"
|
||||
|
||||
#: core/models.py
|
||||
msgid "expires at"
|
||||
msgstr "expire le"
|
||||
@@ -1925,6 +1853,10 @@ msgstr "taille"
|
||||
msgid "date"
|
||||
msgstr "date"
|
||||
|
||||
#: core/models.py counter/models.py
|
||||
msgid "updated at"
|
||||
msgstr "mis à jour le"
|
||||
|
||||
#: core/models.py
|
||||
msgid "asked for removal"
|
||||
msgstr "retrait demandé"
|
||||
@@ -3395,6 +3327,10 @@ msgstr "prix d'achat"
|
||||
msgid "Initial cost of purchasing the product"
|
||||
msgstr "Coût initial d'achat du produit"
|
||||
|
||||
#: counter/models.py
|
||||
msgid "icon"
|
||||
msgstr "icône"
|
||||
|
||||
#: counter/models.py
|
||||
msgid "limit age"
|
||||
msgstr "âge limite"
|
||||
@@ -4505,6 +4441,15 @@ msgstr ""
|
||||
"souhaitez payer par carte, vous devez rajouter un numéro de téléphone aux "
|
||||
"données que vous aviez déjà fourni."
|
||||
|
||||
#: eboutic/views.py
|
||||
msgid "Basket expired"
|
||||
msgstr "Panier expiré"
|
||||
|
||||
#: eboutic/views.py
|
||||
#, python-format
|
||||
msgid "Basket available until %(until)s"
|
||||
msgstr "Panier disponible jusqu'à %(until)s"
|
||||
|
||||
#: eboutic/views.py
|
||||
msgid "You can't buy a refilling with sith money"
|
||||
msgstr "Vous ne pouvez pas acheter un rechargement avec de l'argent du sith"
|
||||
@@ -5561,8 +5506,10 @@ msgid "Amicale/DOCEO member"
|
||||
msgstr "Membre de l'Amicale/DOCEO"
|
||||
|
||||
#: sith/settings.py
|
||||
#, fuzzy
|
||||
#| msgid "UT network member"
|
||||
msgid "UT network member (excluding UTC)"
|
||||
msgstr "Cotisant du réseau UT (hors UTC)"
|
||||
msgstr "Cotisant du réseau UT"
|
||||
|
||||
#: sith/settings.py
|
||||
msgid "CROUS member"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-17 22:42+0200\n"
|
||||
"POT-Creation-Date: 2026-05-17 10:03+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"
|
||||
@@ -263,6 +263,10 @@ msgstr "Types de produits réordonnés !"
|
||||
msgid "Product type reorganisation failed with status code : %d"
|
||||
msgstr "La réorganisation des types de produit a échoué avec le code : %d"
|
||||
|
||||
#: eboutic/static/bundled/eboutic/checkout-index.ts
|
||||
msgid "Basket expired"
|
||||
msgstr "Panier expiré"
|
||||
|
||||
#: sas/static/bundled/sas/pictures-download-index.ts
|
||||
msgid "pictures.%(extension)s"
|
||||
msgstr "photos.%(extension)s"
|
||||
|
||||
Generated
+198
-216
@@ -9,7 +9,7 @@
|
||||
"version": "3",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@alpinejs/sort": "^3.15.12",
|
||||
"@alpinejs/sort": "^3.15.11",
|
||||
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
|
||||
"@floating-ui/dom": "^1.7.6",
|
||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||
@@ -17,31 +17,31 @@
|
||||
"@fullcalendar/daygrid": "^6.1.20",
|
||||
"@fullcalendar/icalendar": "^6.1.20",
|
||||
"@fullcalendar/list": "^6.1.20",
|
||||
"@sentry/browser": "^10.53.1",
|
||||
"@sentry/browser": "^10.51.0",
|
||||
"@zip.js/zip.js": "^2.8.26",
|
||||
"3d-force-graph": "^1.80.0",
|
||||
"alpinejs": "^3.15.12",
|
||||
"alpinejs": "^3.15.11",
|
||||
"chart.js": "^4.5.1",
|
||||
"country-flag-emoji-polyfill": "^0.1.8",
|
||||
"cytoscape": "^3.33.4",
|
||||
"cytoscape": "^3.33.2",
|
||||
"cytoscape-cxtmenu": "^3.5.0",
|
||||
"cytoscape-klay": "^3.1.4",
|
||||
"d3-force-3d": "^3.0.6",
|
||||
"easymde": "^2.21.0",
|
||||
"easymde": "^2.20.0",
|
||||
"glob": "^13.0.6",
|
||||
"html2canvas": "^1.4.1",
|
||||
"htmx.org": "^2.0.10",
|
||||
"js-cookie": "^3.0.7",
|
||||
"lit-html": "^3.3.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lit-html": "^3.3.2",
|
||||
"native-file-system-adapter": "^3.0.1",
|
||||
"three": "^0.184.0",
|
||||
"three-spritetext": "^1.10.0",
|
||||
"tom-select": "^2.6.1"
|
||||
"tom-select": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@biomejs/biome": "^2.4.13",
|
||||
"@hey-api/openapi-ts": "^0.94.5",
|
||||
"@types/alpinejs": "^3.13.11",
|
||||
"@types/cytoscape-cxtmenu": "^3.4.5",
|
||||
@@ -49,13 +49,13 @@
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.13"
|
||||
"vite": "^8.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@alpinejs/sort": {
|
||||
"version": "3.15.12",
|
||||
"resolved": "https://registry.npmjs.org/@alpinejs/sort/-/sort-3.15.12.tgz",
|
||||
"integrity": "sha512-DNIS7SQFg4H4o5faluRgqYEPi1Q7Hf+HMDgoCcIHXbXWH0g66WPEzz9OMk1zsUYib0KxMms4xXOqk+xh2tHZzg==",
|
||||
"version": "3.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@alpinejs/sort/-/sort-3.15.11.tgz",
|
||||
"integrity": "sha512-HaDZ0jP7OYjRJ8Pv1aErOd3EaILq6pQWr+g3ZY9ddHTA37o1NoLfe20tu8AI6SOs3dppqxykXG99RTn/tsyShA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sortablejs": "^1.15.2"
|
||||
@@ -83,9 +83,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
|
||||
"integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
|
||||
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -480,23 +480,6 @@
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": {
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz",
|
||||
"integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz",
|
||||
@@ -1016,9 +999,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz",
|
||||
"integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1451,20 +1434,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/preset-env": {
|
||||
"version": "7.29.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz",
|
||||
"integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==",
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz",
|
||||
"integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.29.3",
|
||||
"@babel/compat-data": "^7.29.0",
|
||||
"@babel/helper-compilation-targets": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-validator-option": "^7.27.1",
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
|
||||
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
|
||||
"@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3",
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6",
|
||||
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
|
||||
@@ -1496,7 +1478,7 @@
|
||||
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-amd": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.29.4",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.29.0",
|
||||
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0",
|
||||
"@babel/plugin-transform-new-target": "^7.27.1",
|
||||
@@ -1609,9 +1591,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.15.tgz",
|
||||
"integrity": "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.13.tgz",
|
||||
"integrity": "sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
@@ -1625,20 +1607,20 @@
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "2.4.15",
|
||||
"@biomejs/cli-darwin-x64": "2.4.15",
|
||||
"@biomejs/cli-linux-arm64": "2.4.15",
|
||||
"@biomejs/cli-linux-arm64-musl": "2.4.15",
|
||||
"@biomejs/cli-linux-x64": "2.4.15",
|
||||
"@biomejs/cli-linux-x64-musl": "2.4.15",
|
||||
"@biomejs/cli-win32-arm64": "2.4.15",
|
||||
"@biomejs/cli-win32-x64": "2.4.15"
|
||||
"@biomejs/cli-darwin-arm64": "2.4.13",
|
||||
"@biomejs/cli-darwin-x64": "2.4.13",
|
||||
"@biomejs/cli-linux-arm64": "2.4.13",
|
||||
"@biomejs/cli-linux-arm64-musl": "2.4.13",
|
||||
"@biomejs/cli-linux-x64": "2.4.13",
|
||||
"@biomejs/cli-linux-x64-musl": "2.4.13",
|
||||
"@biomejs/cli-win32-arm64": "2.4.13",
|
||||
"@biomejs/cli-win32-x64": "2.4.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.15.tgz",
|
||||
"integrity": "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.13.tgz",
|
||||
"integrity": "sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1653,9 +1635,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz",
|
||||
"integrity": "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.13.tgz",
|
||||
"integrity": "sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1670,9 +1652,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz",
|
||||
"integrity": "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.13.tgz",
|
||||
"integrity": "sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1690,9 +1672,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz",
|
||||
"integrity": "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.13.tgz",
|
||||
"integrity": "sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1710,9 +1692,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz",
|
||||
"integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.13.tgz",
|
||||
"integrity": "sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1730,9 +1712,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz",
|
||||
"integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.13.tgz",
|
||||
"integrity": "sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1750,9 +1732,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz",
|
||||
"integrity": "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.13.tgz",
|
||||
"integrity": "sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1767,9 +1749,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz",
|
||||
"integrity": "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==",
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.13.tgz",
|
||||
"integrity": "sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2109,9 +2091,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.130.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
|
||||
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
|
||||
"version": "0.127.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
|
||||
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -2119,9 +2101,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
|
||||
"integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2136,9 +2118,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
|
||||
"integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2153,9 +2135,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
|
||||
"integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2170,9 +2152,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
|
||||
"integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2187,9 +2169,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
|
||||
"integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2204,9 +2186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2224,9 +2206,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
|
||||
"integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2244,9 +2226,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2264,9 +2246,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2284,9 +2266,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2304,9 +2286,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz",
|
||||
"integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2324,9 +2306,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz",
|
||||
"integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2341,9 +2323,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz",
|
||||
"integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -2360,9 +2342,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
|
||||
"integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2377,9 +2359,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz",
|
||||
"integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2394,82 +2376,82 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.53.1.tgz",
|
||||
"integrity": "sha512-X4d6y8sBMjmNhcDW4eMBU3ASsNIMz8dqaFkhyIMN/dkYr/yZKnbRZPaVuVUGvHKjnlficPpIH0/HK9KBjrYxPw==",
|
||||
"version": "10.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.51.0.tgz",
|
||||
"integrity": "sha512-lNKBS4P7RUvf1niojXQWe9bU3gnBUCbST4Dj0pSiyat1N96cXVyHkeE+uGxowD0RrVWhs+kGHiVX3FcmRWF6sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "10.53.1"
|
||||
"@sentry/core": "10.51.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.53.1.tgz",
|
||||
"integrity": "sha512-vVpTI/aEYN5d9IgZeYJWMqVaN0+iFgidSrYNAsZTh1US5sJUzF/wrl+68KdpmCtFROrN3jiAn1oPSwL5CKvEJA==",
|
||||
"version": "10.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.51.0.tgz",
|
||||
"integrity": "sha512-bCM95bcpphx28e6aU0bwRLxOgwosYsdNzezM1sM0pVOkb0TB3hDFRamramVDK+/Hp1o8qmRxS4c5w/A7YBZGkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "10.53.1"
|
||||
"@sentry/core": "10.51.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.53.1.tgz",
|
||||
"integrity": "sha512-wZNzTBYkgGUPWMuUQv7L64+OJmoCnz7GQNiTrTFK6EVAjJXFBCSsPp/nhif0bLhbk8+0g4xz633uOhpXuQbFdw==",
|
||||
"version": "10.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.51.0.tgz",
|
||||
"integrity": "sha512-jCpI5HXSwK6ZT2HX70+mDRciAocHzSiDk4DTgvzV69Wvd+Ei5WLgE+d39eaEPsm8lUC0Ydntb5sJIB6uG9D4bw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "10.53.1",
|
||||
"@sentry/core": "10.53.1"
|
||||
"@sentry-internal/browser-utils": "10.51.0",
|
||||
"@sentry/core": "10.51.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.53.1.tgz",
|
||||
"integrity": "sha512-aueLaf/2prExwA76BGU5/bOXCKWqtt6jQXWA6WJQNrmKpPEtZJB4ypnpsou0McXQCF8tur2Y8U0TEkwQP13yJQ==",
|
||||
"version": "10.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.51.0.tgz",
|
||||
"integrity": "sha512-8PW1Pp+Yl3lPwYqhBCr5SgkuhDanu9ZLzUqD2bPKL/ElqbM2eDVIWxq4z4ZzePrmZa6IcCjTv6sVQJ7Z4dLyLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "10.53.1",
|
||||
"@sentry/core": "10.53.1"
|
||||
"@sentry-internal/replay": "10.51.0",
|
||||
"@sentry/core": "10.51.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.53.1.tgz",
|
||||
"integrity": "sha512-zXF373hzUOGzUOrqd8xb1U3LQi5uYC3mwv+z5OMKUUinQlu30tTWBs7ypy6YTchtix9QlYaHWlayUF8vBZ5UjA==",
|
||||
"version": "10.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.51.0.tgz",
|
||||
"integrity": "sha512-Zdc0sKfenxUtW/OGhtJ7xHFN44bXR7YqxJ1zBDzlZfW0nTbeTTUZBq9z5NUw6qdS0Vs/i3V4qzAKTbRKWfqSEA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "10.53.1",
|
||||
"@sentry-internal/feedback": "10.53.1",
|
||||
"@sentry-internal/replay": "10.53.1",
|
||||
"@sentry-internal/replay-canvas": "10.53.1",
|
||||
"@sentry/core": "10.53.1"
|
||||
"@sentry-internal/browser-utils": "10.51.0",
|
||||
"@sentry-internal/feedback": "10.51.0",
|
||||
"@sentry-internal/replay": "10.51.0",
|
||||
"@sentry-internal/replay-canvas": "10.51.0",
|
||||
"@sentry/core": "10.51.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "10.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.53.1.tgz",
|
||||
"integrity": "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA==",
|
||||
"version": "10.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.51.0.tgz",
|
||||
"integrity": "sha512-Y45V/YXvVLEXmOdkbD1oG1gkRWFi9guCEGg3PlIlIpRjAbZUrvLGgjRJIc1E7XpSzmOnWbs5BbUxMv4PDaPj2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -2482,9 +2464,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -2621,9 +2603,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.15.12",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.12.tgz",
|
||||
"integrity": "sha512-nJvPAQVNPdZZ0NrExJ/kzQco3ijR8LwvCOadQecllESiqT4NyZ/57sN9V2XyvhlBGAbmlKYgeWZvYdKq99ij/Q==",
|
||||
"version": "3.15.11",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.11.tgz",
|
||||
"integrity": "sha512-m26gkTg/MId8O+F4jHKK3vB3SjbFxxk/JHP+qzmw1H6aQrZuPAg4CUoAefnASzzp/eNroBjrRQe7950bNeaBJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
@@ -2746,9 +2728,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
@@ -3014,9 +2996,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cytoscape": {
|
||||
"version": "3.33.4",
|
||||
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.4.tgz",
|
||||
"integrity": "sha512-HIN5Pmd9MrX9BkV7tDwnOcEJCSFvCpc8X97h3f508J6I5FsqAY65wKOCvgH2CuP42CaahWaz4tuh32SOOIH7ww==",
|
||||
"version": "3.33.2",
|
||||
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz",
|
||||
"integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
@@ -3313,9 +3295,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/easymde": {
|
||||
"version": "2.21.0",
|
||||
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.21.0.tgz",
|
||||
"integrity": "sha512-5uE7I/DEN8gvGRwxaqAv7h1PMEK2ykNXVX5zL0dK3nCYROGja3AMbdQz8eCEELnfvCfy7tRkTmLuvyJG8uSWjQ==",
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.20.0.tgz",
|
||||
"integrity": "sha512-V1Z5f92TfR42Na852OWnIZMbM7zotWQYTddNaLYZFVKj7APBbyZ3FYJ27gBw2grMW3R6Qdv9J8n5Ij7XRSIgXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/codemirror": "^5.60.10",
|
||||
@@ -3674,12 +3656,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz",
|
||||
"integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
@@ -4008,9 +3990,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lit-html": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz",
|
||||
"integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==",
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz",
|
||||
"integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2"
|
||||
@@ -4083,9 +4065,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4350,9 +4332,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"version": "8.5.12",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
|
||||
"integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4370,7 +4352,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.12",
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
@@ -4516,14 +4498,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
|
||||
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.130.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
"@oxc-project/types": "=0.127.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.17"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
@@ -4532,21 +4514,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.1",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.1",
|
||||
"@rolldown/binding-darwin-x64": "1.0.1",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.1",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.1",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.1",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.1",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.1",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.1",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.1",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.1"
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-plugin-visualizer": {
|
||||
@@ -4803,9 +4785,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tom-select": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/tom-select/-/tom-select-2.6.1.tgz",
|
||||
"integrity": "sha512-d/1kngVOQTGcI/2pVDfDLYjtjUgSSd3fSgkYUpi0y+yRtQQu2kzljj3aUdqMfqc45cjPvDEpfDt/hSX4awDFTg==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tom-select/-/tom-select-2.6.0.tgz",
|
||||
"integrity": "sha512-o2ToBjhUAnrrQvW/hrY9c//TpOpAKYSlfuFnf0DIwNy+ua+mmYnsF4PxN/PpzBfUIfEFkNYAngeGBfOAZWF3tw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@orchidjs/sifter": "^1.1.0",
|
||||
@@ -4932,16 +4914,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.13",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
|
||||
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
|
||||
"version": "8.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
|
||||
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.14",
|
||||
"rolldown": "1.0.1",
|
||||
"postcss": "^8.5.10",
|
||||
"rolldown": "1.0.0-rc.17",
|
||||
"tinyglobby": "^0.2.16"
|
||||
},
|
||||
"bin": {
|
||||
@@ -4958,7 +4940,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"@vitejs/devtools": "^0.1.18",
|
||||
"@vitejs/devtools": "^0.1.0",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
|
||||
+11
-11
@@ -25,8 +25,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@biomejs/biome": "^2.4.13",
|
||||
"@hey-api/openapi-ts": "^0.94.5",
|
||||
"@types/alpinejs": "^3.13.11",
|
||||
"@types/cytoscape-cxtmenu": "^3.4.5",
|
||||
@@ -34,10 +34,10 @@
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.13"
|
||||
"vite": "^8.0.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alpinejs/sort": "^3.15.12",
|
||||
"@alpinejs/sort": "^3.15.11",
|
||||
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
|
||||
"@floating-ui/dom": "^1.7.6",
|
||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||
@@ -45,25 +45,25 @@
|
||||
"@fullcalendar/daygrid": "^6.1.20",
|
||||
"@fullcalendar/icalendar": "^6.1.20",
|
||||
"@fullcalendar/list": "^6.1.20",
|
||||
"@sentry/browser": "^10.53.1",
|
||||
"@sentry/browser": "^10.51.0",
|
||||
"@zip.js/zip.js": "^2.8.26",
|
||||
"3d-force-graph": "^1.80.0",
|
||||
"alpinejs": "^3.15.12",
|
||||
"alpinejs": "^3.15.11",
|
||||
"chart.js": "^4.5.1",
|
||||
"country-flag-emoji-polyfill": "^0.1.8",
|
||||
"cytoscape": "^3.33.4",
|
||||
"cytoscape": "^3.33.2",
|
||||
"cytoscape-cxtmenu": "^3.5.0",
|
||||
"cytoscape-klay": "^3.1.4",
|
||||
"d3-force-3d": "^3.0.6",
|
||||
"easymde": "^2.21.0",
|
||||
"easymde": "^2.20.0",
|
||||
"glob": "^13.0.6",
|
||||
"html2canvas": "^1.4.1",
|
||||
"htmx.org": "^2.0.10",
|
||||
"js-cookie": "^3.0.7",
|
||||
"lit-html": "^3.3.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lit-html": "^3.3.2",
|
||||
"native-file-system-adapter": "^3.0.1",
|
||||
"three": "^0.184.0",
|
||||
"three-spritetext": "^1.10.0",
|
||||
"tom-select": "^2.6.1"
|
||||
"tom-select": "^2.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
+31
-31
@@ -19,38 +19,38 @@ authors = [
|
||||
license = { text = "GPL-3.0-only" }
|
||||
requires-python = "<4.0,>=3.12"
|
||||
dependencies = [
|
||||
"django>=5.2.14,<6.0.0",
|
||||
"django-ninja>=1.6.2,<2.0.0",
|
||||
"django>=5.2.13,<6.0.0",
|
||||
"django-ninja>=1.6.2,<6.0.0",
|
||||
"django-ninja-extra>=0.31.4",
|
||||
"Pillow>=12.2.0,<13.0.0",
|
||||
"mistune>=3.2.1,<4.0.0",
|
||||
"mistune>=3.2.0,<4.0.0",
|
||||
"django-jinja<3.0.0,>=2.11.0",
|
||||
"cryptography>=48.0.0,<49.0.0",
|
||||
"cryptography>=47.0.0,<48.0.0",
|
||||
"django-phonenumber-field>=8.4.0,<9.0.0",
|
||||
"phonenumbers>=9.0.30,<10.0.0",
|
||||
"reportlab>=4.5.1,<5.0.0",
|
||||
"phonenumbers>=9.0.29,<10.0.0",
|
||||
"reportlab>=4.5.0,<5.0.0",
|
||||
"django-haystack>=3.3.0,<4.0.0",
|
||||
"xapian-haystack>=4.0.0,<5.0.0",
|
||||
"libsass>=0.23.0,<1.0.0",
|
||||
"django-ordered-model>=3.7.4,<4.0.0",
|
||||
"django-simple-captcha>=0.6.3,<1.0.0",
|
||||
"python-dateutil>=2.9.0.post0,<3.0.0.0",
|
||||
"sentry-sdk>=2.60.0,<3.0.0",
|
||||
"jinja2>=3.1.6,<4.0.0",
|
||||
"libsass<1.0.0,>=0.23.0",
|
||||
"django-ordered-model<4.0.0,>=3.7.4",
|
||||
"django-simple-captcha<1.0.0,>=0.6.3",
|
||||
"python-dateutil<3.0.0.0,>=2.9.0.post0",
|
||||
"sentry-sdk>=2.58.0,<3.0.0",
|
||||
"jinja2<4.0.0,>=3.1.6",
|
||||
"django-countries>=8.2.0,<9.0.0",
|
||||
"dict2xml>=1.7.8,<2.0.0",
|
||||
"Sphinx>=9.1.0,<10", # Used by xapian during installation
|
||||
"Sphinx<6,>=5",
|
||||
"tomli>=2.4.1,<3.0.0",
|
||||
"django-honeypot>=1.3.0,<2",
|
||||
"pydantic-extra-types>=2.11.1,<3.0.0",
|
||||
"ical>=12.0.0,<14.0.0",
|
||||
"redis[hiredis]>=3.3.1,<8.0.0",
|
||||
"environs[django]>=15.0.1,<16",
|
||||
"requests>=2.34.2,<3.0.0",
|
||||
"ical>=11.1.0,<14.0.0",
|
||||
"redis[hiredis]>=6.4.0,<8.0.0",
|
||||
"environs[django]>=15.0.1,<16.0.0",
|
||||
"requests>=2.32.5,<3.0.0",
|
||||
"honcho>=2.0.0",
|
||||
"psutil>=7.2.2,<8.0.0",
|
||||
"celery[redis]>=5.6.3,<8",
|
||||
"django-celery-results>=2.6.0",
|
||||
"celery[redis]>=5.6.2,<7",
|
||||
"django-celery-results>=2.5.1",
|
||||
"django-celery-beat>=2.9.0",
|
||||
]
|
||||
|
||||
@@ -60,43 +60,43 @@ documentation = "https://sith-ae.readthedocs.io/"
|
||||
|
||||
[dependency-groups]
|
||||
prod = [
|
||||
"psycopg[c]>=3.3.4,<4.0.0",
|
||||
"psycopg[c]>=3.3.3,<4.0.0",
|
||||
]
|
||||
dev = [
|
||||
"django-debug-toolbar>=6.3.0,<7",
|
||||
"ipython>=9.13.0,<10.0.0",
|
||||
"pre-commit>=4.6.0,<5.0.0",
|
||||
"ruff>=0.15.13,<1.0.0",
|
||||
"ruff>=0.15.12,<1.0.0",
|
||||
"djhtml>=3.0.11,<4.0.0",
|
||||
"faker>=40.18.0,<41.0.0",
|
||||
"faker>=40.15.0,<41.0.0",
|
||||
"rjsmin>=1.2.5,<2.0.0",
|
||||
]
|
||||
tests = [
|
||||
"freezegun>=1.5.5,<2.0.0",
|
||||
"pytest>=9.0.3,<10.0.0",
|
||||
"pytest-cov>=7.1.0,<8.0.0",
|
||||
"pytest-django>=4.12.0,<5.0.0",
|
||||
"model-bakery>=1.23.4,<2.0.0",
|
||||
"pytest-django<5.0.0,>=4.12.0",
|
||||
"model-bakery<2.0.0,>=1.23.4",
|
||||
"beautifulsoup4>=4.14.3,<5",
|
||||
"lxml>=6.1.1,<7",
|
||||
"lxml>=6.1.0,<7",
|
||||
]
|
||||
docs = [
|
||||
"mkdocs>=1.6.1,<2.0.0",
|
||||
"mkdocs<2.0.0,>=1.6.1",
|
||||
"mkdocs-material>=9.7.6,<10.0.0",
|
||||
"mkdocstrings>=1.0.4,<2.0.0",
|
||||
"mkdocstrings-python>=2.0.3,<3.0.0",
|
||||
"mkdocs-include-markdown-plugin>=7.3.0,<8.0.0",
|
||||
"mkdocs-include-markdown-plugin>=7.2.2,<8.0.0",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
default-groups = ["dev", "tests", "docs"]
|
||||
|
||||
[tool.xapian]
|
||||
version = "2.0.0"
|
||||
version = "1.4.31"
|
||||
# Those hashes are here to protect against supply chains attacks
|
||||
# See `https://ae-utbm.github.io/sith/howto/xapian/` for more information
|
||||
core-sha256 = "6cea3f49952a47224439a40bdb3608f928d121ad8721b9921cc42802d548ecf8"
|
||||
bindings-sha256 = "9a544b69c31355a92edbcd4102cf0f1ec4407fd0a4645f4870fb52300b736910"
|
||||
core-sha256 = "fecf609ea2efdc8a64be369715aac733336a11f7480a6545244964ae6bc80811"
|
||||
bindings-sha256 = "a38cc7ba4188cc0bd27dc7369f03906772047087a1c54f1b93355d5e9103c304"
|
||||
|
||||
[tool.ruff]
|
||||
output-format = "concise" # makes ruff error logs easier to read
|
||||
@@ -145,4 +145,4 @@ sith = "sith.pytest"
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "sith.settings"
|
||||
python_files = ["tests.py", "test_*.py", "*_tests.py"]
|
||||
markers = ["slow"]
|
||||
markers = ["slow"]
|
||||
+5
-5
@@ -88,11 +88,6 @@ X_FRAME_OPTIONS = "SAMEORIGIN"
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
# RemovedInDjango60Warning: It's a transitional setting helpful in early
|
||||
# adoption of "https" as the new default value of forms.URLField.assume_scheme.
|
||||
# Remove this after upgrading to Django 6.x
|
||||
FORMS_URLFIELD_ASSUME_HTTPS = True
|
||||
|
||||
# Application definition
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
@@ -571,6 +566,11 @@ SITH_BARMAN_TIMEOUT = 30
|
||||
# Minutes to delete the last operations
|
||||
SITH_LAST_OPERATIONS_LIMIT = 10
|
||||
|
||||
# time before a basket is considered expired
|
||||
SITH_EBOUTIC_BASKET_TIMEOUT = timedelta(minutes=10)
|
||||
# time that a user can spend on the CB payment page before it to timeout
|
||||
SITH_EBOUTIC_ETRANSACTION_TIMEOUT = timedelta(minutes=10)
|
||||
|
||||
# ET variables
|
||||
SITH_EBOUTIC_CB_ENABLED = env.bool("SITH_EBOUTIC_CB_ENABLED", default=True)
|
||||
SITH_EBOUTIC_ET_URL = env.str(
|
||||
|
||||
Reference in New Issue
Block a user