mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-29 06:49:40 +00:00
Compare commits
23 Commits
dependabot
...
club-list
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd26b279c1 | ||
|
|
7a69b3441e | ||
|
|
36edcf19d7 | ||
|
|
af59ebc896 | ||
|
|
f6830ebd9b | ||
|
|
182cdbe590 | ||
|
|
ac33a5e6b2 | ||
|
|
068bb9ab83 | ||
|
|
f9910c3360 | ||
|
|
f0f8cc5604 | ||
|
|
2a8e810ad0 | ||
|
|
739a1bba47 | ||
|
|
180852a598 | ||
|
|
c3989a0016 | ||
|
|
435c8f9612 | ||
|
|
3d7f57b8da | ||
|
|
ffa0b94408 | ||
|
|
22a1f4ba07 | ||
|
|
76396cdeb0 | ||
|
|
1c0b89bfc7 | ||
|
|
d374ea9651 | ||
|
|
10a4e71b7a | ||
|
|
f1a60e589a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -24,6 +24,9 @@ node_modules/
|
||||
# compiled documentation
|
||||
site/
|
||||
|
||||
# rollup-bundle-visualizer report
|
||||
.bundle-size-report.html
|
||||
|
||||
### Redis ###
|
||||
|
||||
# Ignore redis binary dump (dump.rdb) files
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": ["**/static/**"]
|
||||
"includes": ["**/static/**", "vite.config.mts"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
|
||||
17
club/api.py
17
club/api.py
@@ -6,9 +6,10 @@ from ninja_extra.pagination import PageNumberPaginationExtra
|
||||
from ninja_extra.schemas import PaginatedResponseSchema
|
||||
|
||||
from api.auth import ApiKeyAuth
|
||||
from api.permissions import CanAccessLookup, CanView, HasPerm
|
||||
from api.permissions import CanView, HasPerm
|
||||
from club.models import Club, Membership
|
||||
from club.schemas import (
|
||||
ClubProfileSchema,
|
||||
ClubSchema,
|
||||
ClubSearchFilterSchema,
|
||||
SimpleClubSchema,
|
||||
@@ -22,13 +23,21 @@ class ClubController(ControllerBase):
|
||||
@route.get(
|
||||
"/search",
|
||||
response=PaginatedResponseSchema[SimpleClubSchema],
|
||||
auth=[ApiKeyAuth(), SessionAuth()],
|
||||
permissions=[CanAccessLookup],
|
||||
url_name="search_club",
|
||||
)
|
||||
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||
def search_club(self, filters: Query[ClubSearchFilterSchema]):
|
||||
return filters.filter(Club.objects.all())
|
||||
return filters.filter(Club.objects.order_by("name")).values()
|
||||
|
||||
@route.get(
|
||||
"/search-profile",
|
||||
response=PaginatedResponseSchema[ClubProfileSchema],
|
||||
url_name="search_club_profile",
|
||||
)
|
||||
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||
def search_club_profile(self, filters: Query[ClubSearchFilterSchema]):
|
||||
"""Same as /api/club/search, but with more returned data"""
|
||||
return filters.filter(Club.objects.order_by("name"))
|
||||
|
||||
@route.get(
|
||||
"/{int:club_id}",
|
||||
|
||||
@@ -315,3 +315,22 @@ class JoinClubForm(ClubMemberForm):
|
||||
_("You are already a member of this club"), code="invalid"
|
||||
)
|
||||
return super().clean()
|
||||
|
||||
|
||||
class ClubSearchForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Club
|
||||
fields = ["name"]
|
||||
widgets = {"name": forms.SearchInput(attrs={"autocomplete": "off"})}
|
||||
|
||||
club_status = forms.NullBooleanField(
|
||||
label=_("Club status"),
|
||||
widget=forms.RadioSelect(
|
||||
choices=[(True, _("Active")), (False, _("Inactive")), ("", _("All clubs"))],
|
||||
),
|
||||
initial=True,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["name"].required = False
|
||||
|
||||
@@ -30,7 +30,7 @@ class ClubProfileSchema(ModelSchema):
|
||||
|
||||
class Meta:
|
||||
model = Club
|
||||
fields = ["id", "name", "logo"]
|
||||
fields = ["id", "name", "logo", "is_active", "short_description"]
|
||||
|
||||
url: str
|
||||
|
||||
|
||||
47
club/static/club/list.scss
Normal file
47
club/static/club/list.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
#club-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2em;
|
||||
padding: 2em;
|
||||
|
||||
.card {
|
||||
display: block;
|
||||
background-color: unset;
|
||||
|
||||
.club-image {
|
||||
float: left;
|
||||
margin-right: 2rem;
|
||||
margin-bottom: .5rem;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 10%;
|
||||
background-color: rgba(173, 173, 173, 0.2);
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
i.club-image {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from "core/macros.jinja" import paginate_jinja with context %}
|
||||
|
||||
{% block title -%}
|
||||
{% trans %}Club list{% endtrans %}
|
||||
@@ -8,45 +9,50 @@
|
||||
{% trans %}The list of all clubs existing at UTBM.{% endtrans %}
|
||||
{%- endblock %}
|
||||
|
||||
{% macro display_club(club) -%}
|
||||
|
||||
{% if club.is_active or user.is_root %}
|
||||
|
||||
<li><a href="{{ url('club:club_view', club_id=club.id) }}">{{ club.name }}</a>
|
||||
|
||||
{% if not club.is_active %}
|
||||
({% trans %}inactive{% endtrans %})
|
||||
{% endif %}
|
||||
|
||||
{% if club.president %} - <a href="{{ url('core:user_profile', user_id=club.president.user.id) }}">{{ club.president.user }}</a>{% endif %}
|
||||
{% if club.short_description %}<p>{{ club.short_description|markdown }}</p>{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{%- if club.children.all()|length != 0 %}
|
||||
<ul>
|
||||
{%- for c in club.children.order_by('name').prefetch_related("children") %}
|
||||
{{ display_club(c) }}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{%- endmacro %}
|
||||
{% block additional_css %}
|
||||
<link rel="stylesheet" href="{{ static("club/list.scss") }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if user.is_root %}
|
||||
<p><a href="{{ url('club:club_new') }}">{% trans %}New club{% endtrans %}</a></p>
|
||||
{% endif %}
|
||||
{% if club_list %}
|
||||
<main>
|
||||
<h3>{% trans %}Filters{% endtrans %}</h3>
|
||||
<form id="club-list-filters" method="GET">
|
||||
<div class="row gap-4x">
|
||||
{{ form }}
|
||||
</div>
|
||||
<input type="submit" class="btn btn-blue margin-bottom" value="{% trans %}Search{% endtrans %}">
|
||||
</form>
|
||||
<h3>{% trans %}Club list{% endtrans %}</h3>
|
||||
<ul>
|
||||
{%- for club in club_list %}
|
||||
{{ display_club(club) }}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{% trans %}There is no club in this website.{% endtrans %}
|
||||
{% if user.has_perm("club.add_club") %}
|
||||
<br>
|
||||
<a href="{{ url('club:club_new') }}" class="btn btn-blue">
|
||||
<i class="fa fa-plus"></i> {% trans %}New club{% endtrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<section class="aria-busy-grow" id="club-list">
|
||||
{% for club in object_list %}
|
||||
<div class="card">
|
||||
{% set club_url = club.get_absolute_url() %}
|
||||
<a href="{{ club_url }}">
|
||||
{% if club.logo %}
|
||||
<img class="club-image" src="{{ club.logo.url }}" alt="logo {{ club.name }}">
|
||||
{% else %}
|
||||
<i class="fa-regular fa-image fa-4x club-image"></i>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="content">
|
||||
<a href="{{ club_url }}">
|
||||
<h4>
|
||||
{{ club.name }} {% if not club.is_active %}({% trans %}inactive{% endtrans %}){% endif %}
|
||||
</h4>
|
||||
</a>
|
||||
{{ club.short_description|markdown }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import localdate
|
||||
from model_bakery import baker
|
||||
from model_bakery.recipe import Recipe
|
||||
|
||||
from club.models import Club, Membership
|
||||
from core.baker_recipes import subscriber_user
|
||||
from core.models import User
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -25,3 +28,10 @@ def test_club_queryset_having_board_member():
|
||||
|
||||
club_ids = Club.objects.having_board_member(user).values_list("id", flat=True)
|
||||
assert set(club_ids) == {clubs[1].id, clubs[2].id}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_club_list(client: Client):
|
||||
client.force_login(baker.make(User))
|
||||
res = client.get(reverse("club:club_list"))
|
||||
assert res.status_code == 200
|
||||
|
||||
@@ -59,6 +59,14 @@ class TestClubSearch(TestCase):
|
||||
ids = {d["id"] for d in response.json()["results"]}
|
||||
assert ids == {c.id for c in [self.clubs[0], self.clubs[1], self.clubs[3]]}
|
||||
|
||||
def test_club_search_profile(self):
|
||||
self.client.force_login(self.user)
|
||||
url = reverse("api:search_club_profile")
|
||||
response = self.client.get(url, {"search": "AE"})
|
||||
assert response.status_code == 200
|
||||
ids = {d["id"] for d in response.json()["results"]}
|
||||
assert ids == {c.id for c in [self.clubs[0], self.clubs[1], self.clubs[3]]}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestFetchClub:
|
||||
|
||||
@@ -44,13 +44,19 @@ 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, UpdateView
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
FormMixin,
|
||||
UpdateView,
|
||||
)
|
||||
|
||||
from club.forms import (
|
||||
ClubAddMemberForm,
|
||||
ClubAdminEditForm,
|
||||
ClubEditForm,
|
||||
ClubOldMemberForm,
|
||||
ClubSearchForm,
|
||||
JoinClubForm,
|
||||
MailingForm,
|
||||
SellingsForm,
|
||||
@@ -180,15 +186,41 @@ class ClubTabsMixin(TabedViewMixin):
|
||||
return tab_list
|
||||
|
||||
|
||||
class ClubListView(ListView):
|
||||
"""List the Clubs."""
|
||||
class ClubListView(FormMixin, ListView):
|
||||
"""List the clubs of the AE, with a form to perform basic search.
|
||||
|
||||
Notes:
|
||||
This view is fully public, because we want to advertise as much as possible
|
||||
the cultural life of the AE.
|
||||
In accordance with that matter, searching and listing the clubs is done
|
||||
entirely server-side (no AlpineJS involved) ;
|
||||
this is done this way in order to be sure the page is the most accessible
|
||||
and SEO-friendly possible, even if it makes the UX slightly less smooth.
|
||||
"""
|
||||
|
||||
model = Club
|
||||
template_name = "club/club_list.jinja"
|
||||
queryset = (
|
||||
Club.objects.filter(parent=None).order_by("name").prefetch_related("children")
|
||||
)
|
||||
context_object_name = "club_list"
|
||||
form_class = ClubSearchForm
|
||||
queryset = Club.objects.order_by("name")
|
||||
paginate_by = 20
|
||||
|
||||
def get_form_kwargs(self):
|
||||
res = super().get_form_kwargs()
|
||||
if self.request.GET:
|
||||
res |= {"data": self.request.GET, "initial": self.request.GET}
|
||||
return res
|
||||
|
||||
def get_queryset(self):
|
||||
form: ClubSearchForm = self.get_form()
|
||||
qs = self.queryset
|
||||
if not form.is_bound:
|
||||
return qs
|
||||
if not form.is_valid():
|
||||
return qs.none()
|
||||
if name := form.cleaned_data.get("name"):
|
||||
qs = qs.filter(name__icontains=name)
|
||||
if (is_active := form.cleaned_data.get("club_status")) is not None:
|
||||
qs = qs.filter(is_active=is_active)
|
||||
return qs
|
||||
|
||||
|
||||
class ClubView(ClubTabsMixin, DetailView):
|
||||
|
||||
@@ -244,9 +244,8 @@ class NewsListView(TemplateView):
|
||||
.filter(
|
||||
date_of_birth__month=localdate().month,
|
||||
date_of_birth__day=localdate().day,
|
||||
is_viewable=True,
|
||||
role__in=["STUDENT", "FORMER STUDENT"],
|
||||
)
|
||||
.filter(role__in=["STUDENT", "FORMER STUDENT"])
|
||||
.order_by("-date_of_birth"),
|
||||
key=lambda u: u.date_of_birth.year,
|
||||
)
|
||||
|
||||
@@ -63,6 +63,7 @@ class UserAdmin(admin.ModelAdmin):
|
||||
"scrub_pict",
|
||||
"user_permissions",
|
||||
"groups",
|
||||
"whitelisted_users",
|
||||
)
|
||||
inlines = (UserBanInline,)
|
||||
search_fields = ["first_name", "last_name", "username"]
|
||||
|
||||
@@ -116,7 +116,11 @@ class Command(BaseCommand):
|
||||
)
|
||||
main_club.board_group.permissions.add(
|
||||
*Permission.objects.filter(
|
||||
codename__in=["view_subscription", "add_subscription"]
|
||||
codename__in=[
|
||||
"view_subscription",
|
||||
"add_subscription",
|
||||
"view_hidden_user",
|
||||
]
|
||||
)
|
||||
)
|
||||
bar_club = Club.objects.create(
|
||||
|
||||
37
core/migrations/0049_user_whitelisted_users.py
Normal file
37
core/migrations/0049_user_whitelisted_users.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.2.12 on 2026-03-14 08:39
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("core", "0048_alter_user_options")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="whitelisted_users",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text=(
|
||||
"Even if this profile is hidden, "
|
||||
"the users in this list will still be able to see it."
|
||||
),
|
||||
related_name="visible_by_whitelist",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="whitelisted users",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="preferences",
|
||||
name="show_my_stats",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text=(
|
||||
"Allow subscribers (or whitelisted users "
|
||||
"if your profile is hidden) to access your AE account stats."
|
||||
),
|
||||
verbose_name="show your stats to others",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -131,7 +131,7 @@ class UserQuerySet(models.QuerySet):
|
||||
if user.has_perm("core.view_hidden_user"):
|
||||
return self
|
||||
if user.has_perm("core.view_user"):
|
||||
return self.filter(is_viewable=True)
|
||||
return self.filter(Q(is_viewable=True) | Q(whitelisted_users=user))
|
||||
if user.is_anonymous:
|
||||
return self.none()
|
||||
return self.filter(id=user.id)
|
||||
@@ -279,6 +279,16 @@ class User(AbstractUser):
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
whitelisted_users = models.ManyToManyField(
|
||||
"User",
|
||||
related_name="visible_by_whitelist",
|
||||
verbose_name=_("whitelisted users"),
|
||||
help_text=_(
|
||||
"Even if this profile is hidden, "
|
||||
"the users in this list will still be able to see it."
|
||||
),
|
||||
blank=True,
|
||||
)
|
||||
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
|
||||
|
||||
objects = CustomUserManager()
|
||||
@@ -518,7 +528,7 @@ class User(AbstractUser):
|
||||
self.username = user_name
|
||||
return user_name
|
||||
|
||||
def is_owner(self, obj):
|
||||
def is_owner(self, obj: models.Model):
|
||||
"""Determine if the object is owned by the user."""
|
||||
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
|
||||
return True
|
||||
@@ -526,7 +536,7 @@ class User(AbstractUser):
|
||||
return True
|
||||
return self.is_root
|
||||
|
||||
def can_edit(self, obj):
|
||||
def can_edit(self, obj: models.Model):
|
||||
"""Determine if the object can be edited by the user."""
|
||||
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
|
||||
return True
|
||||
@@ -540,11 +550,9 @@ class User(AbstractUser):
|
||||
pks = list(obj.edit_groups.values_list("id", flat=True))
|
||||
if any(self.is_in_group(pk=pk) for pk in pks):
|
||||
return True
|
||||
if isinstance(obj, User) and obj == self:
|
||||
return True
|
||||
return self.is_owner(obj)
|
||||
|
||||
def can_view(self, obj):
|
||||
def can_view(self, obj: models.Model):
|
||||
"""Determine if the object can be viewed by the user."""
|
||||
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
||||
return True
|
||||
@@ -563,14 +571,35 @@ class User(AbstractUser):
|
||||
return True
|
||||
return self.can_edit(obj)
|
||||
|
||||
def can_be_edited_by(self, user):
|
||||
return user.is_root or user.is_board_member
|
||||
def can_be_edited_by(self, user: User):
|
||||
return user == self or user.is_root or user.is_board_member
|
||||
|
||||
def can_be_viewed_by(self, user: User) -> bool:
|
||||
"""Check if the given user can be viewed by this user.
|
||||
|
||||
Given users A and B. A can be viewed by B if :
|
||||
|
||||
- A and B are the same user
|
||||
- or B has the permission to view hidden users
|
||||
- or B can view users in general and A didn't hide its profile
|
||||
- or B is in A's whitelist.
|
||||
"""
|
||||
|
||||
def is_in_whitelist(u: User):
|
||||
if (
|
||||
hasattr(self, "_prefetched_objects_cache")
|
||||
and "whitelisted_users" in self._prefetched_objects_cache
|
||||
):
|
||||
return u in self.whitelisted_users.all()
|
||||
return self.whitelisted_users.contains(u)
|
||||
|
||||
return (
|
||||
user.id == self.id
|
||||
or user.has_perm("core.view_hidden_user")
|
||||
or (user.has_perm("core.view_user") and self.is_viewable)
|
||||
or (
|
||||
user.has_perm("core.view_user")
|
||||
and (self.is_viewable or is_in_whitelist(user))
|
||||
)
|
||||
)
|
||||
|
||||
def get_mini_item(self):
|
||||
@@ -750,7 +779,14 @@ class Preferences(models.Model):
|
||||
User, related_name="_preferences", on_delete=models.CASCADE
|
||||
)
|
||||
receive_weekmail = models.BooleanField(_("receive the Weekmail"), default=False)
|
||||
show_my_stats = models.BooleanField(_("show your stats to others"), default=False)
|
||||
show_my_stats = models.BooleanField(
|
||||
_("show your stats to others"),
|
||||
help_text=_(
|
||||
"Allow subscribers (or whitelisted users "
|
||||
"if your profile is hidden) to access your AE account stats."
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
notify_on_click = models.BooleanField(
|
||||
_("get a notification for every click"), default=False
|
||||
)
|
||||
|
||||
@@ -26,7 +26,6 @@ export class NfcInput extends inheritHtmlElement("input") {
|
||||
window.alert(gettext("Unsupported NFC card"));
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: browser API
|
||||
ndef.addEventListener("reading", (event: NDEFReadingEvent) => {
|
||||
this.removeAttribute("scan");
|
||||
this.node.value = event.serialNumber.replace(/:/g, "").toUpperCase();
|
||||
|
||||
@@ -157,6 +157,7 @@ form {
|
||||
margin-bottom: .25rem;
|
||||
font-size: 80%;
|
||||
display: block;
|
||||
max-width: calc(100% - calc(var(--nf-input-size) * 2))
|
||||
}
|
||||
|
||||
fieldset {
|
||||
|
||||
@@ -5,17 +5,6 @@
|
||||
}
|
||||
|
||||
.profile {
|
||||
&-visible {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding-top: 10px;
|
||||
input[type="checkbox"]+label {
|
||||
max-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
&-pictures {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
||||
@@ -19,28 +19,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-cards,
|
||||
&-trombi {
|
||||
>p {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
text-align: justify;
|
||||
gap: 5px;
|
||||
margin: 0;
|
||||
|
||||
>input,
|
||||
>select {
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-submit-btn {
|
||||
margin-top: 10px !important;
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.justify {
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
<details name="navbar" class="menu">
|
||||
<summary class="head">{% trans %}Associations & Clubs{% endtrans %}</summary>
|
||||
<ul class="content">
|
||||
<li><a href="{{ url('core:page', page_name='ae') }}">{% trans %}AE{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('core:page', page_name='clubs') }}">{% trans %}AE's clubs{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('core:page', page_name='utbm-associations') }}">{% trans %}Others UTBM's Associations{% endtrans %}</a></li>
|
||||
<li><a href="{{ url("core:page", page_name="ae") }}">{% trans %}AE{% endtrans %}</a></li>
|
||||
<li><a href="{{ url("club:club_list") }}">{% trans %}AE's clubs{% endtrans %}</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details name="navbar" class="menu">
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<div id="quick-notifications"
|
||||
x-data="{
|
||||
messages: [
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
{
|
||||
tag: '{{ message.tags }}',
|
||||
text: '{{ message }}',
|
||||
},
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{%- for message in messages -%}
|
||||
{%- if not message.extra_tags -%}
|
||||
{ tag: '{{ message.tags }}', text: '{{ message }}' },
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
]
|
||||
}"
|
||||
@quick-notification-add="(e) => messages.push(e?.detail)"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% endif %}
|
||||
|
||||
{% from "core/macros.jinja" import paginate_htmx %}
|
||||
{% from "core/macros.jinja" import paginate_htmx with context %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}File moderation{% endtrans %}
|
||||
|
||||
33
core/templates/core/fragment/user_visibility.jinja
Normal file
33
core/templates/core/fragment/user_visibility.jinja
Normal file
@@ -0,0 +1,33 @@
|
||||
<form
|
||||
hx-post="{{ url("core:user_visibility_fragment", user_id=form.instance.id) }}"
|
||||
hx-disabled-elt="find input[type='submit']"
|
||||
hx-swap="outerHTML" x-data="{ isViewable: {{ form.is_viewable.value()|tojson }} }"
|
||||
>
|
||||
{% for message in messages %}
|
||||
{% if message.extra_tags=="visibility" %}
|
||||
<div class="alert alert-success">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors() }}
|
||||
<fieldset class="form-group">
|
||||
{{ form.is_viewable|add_attr("x-model=isViewable") }}
|
||||
{{ form.is_viewable.label_tag() }}
|
||||
<span class="helptext">{{ form.is_viewable.help_text }}</span>
|
||||
{{ form.is_viewable.errors }}
|
||||
</fieldset>
|
||||
<fieldset class="form-group" x-show="!isViewable">
|
||||
{{ form.whitelisted_users.as_field_group() }}
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
{{ form.show_my_stats }}
|
||||
{{ form.show_my_stats.label_tag() }}
|
||||
<span class="helptext">
|
||||
{{ form.show_my_stats.help_text }}
|
||||
</span>
|
||||
{{ form.show_my_stats.errors }}
|
||||
</fieldset>
|
||||
<input type="submit" class="btn btn-blue" value="{% trans %}Save{% endtrans %}">
|
||||
</form>
|
||||
@@ -127,6 +127,12 @@
|
||||
Parameters:
|
||||
current_page (django.core.paginator.Page): the current page object
|
||||
paginator (django.core.paginator.Paginator): the paginator object
|
||||
|
||||
Warnings:
|
||||
This macro must be imported with context :
|
||||
```jinja
|
||||
{% from "core/macros.jinja" import paginate_jinja with context %}
|
||||
```
|
||||
#}
|
||||
{{ paginate_server_side(current_page, paginator, False) }}
|
||||
{% endmacro %}
|
||||
@@ -142,6 +148,12 @@
|
||||
Parameters:
|
||||
current_page (django.core.paginator.Page): the current page object
|
||||
paginator (django.core.paginator.Paginator): the paginator object
|
||||
|
||||
Warnings:
|
||||
This macro must be imported with context :
|
||||
```jinja
|
||||
{% from "core/macros.jinja" import paginate_htmx with context %}
|
||||
```
|
||||
#}
|
||||
{{ paginate_server_side(current_page, paginator, True) }}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -147,18 +147,7 @@
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
|
||||
{# Checkboxes #}
|
||||
<div class="profile-visible">
|
||||
<div class="row">
|
||||
{{ form.is_viewable }}
|
||||
{{ form.is_viewable.label_tag() }}
|
||||
</div>
|
||||
<span class="helptext">
|
||||
{{ form.is_viewable.help_text }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="final-actions">
|
||||
|
||||
{%- if form.instance == user -%}
|
||||
<p>
|
||||
<a href="{{ url('core:password_change') }}">{%- trans -%}Change my password{%- endtrans -%}</a>
|
||||
@@ -170,7 +159,6 @@
|
||||
</a>
|
||||
</p>
|
||||
{%- endif -%}
|
||||
|
||||
<p>
|
||||
<input type="submit" value="{%- trans -%}Update{%- endtrans -%}" />
|
||||
</p>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
|
||||
{%- block additional_js -%}
|
||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block additional_css -%}
|
||||
<link rel="stylesheet" href="{{ static('user/user_preferences.scss') }}">
|
||||
{# importing ajax-select-index is necessary for it to be applied after HTMX reload #}
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||
{%- endblock -%}
|
||||
|
||||
{% block title %}
|
||||
@@ -11,30 +18,22 @@
|
||||
{% block content %}
|
||||
<div class="main">
|
||||
<h2>{% trans %}Preferences{% endtrans %}</h2>
|
||||
<h3>{% trans %}General{% endtrans %}</h3>
|
||||
<form class="form form-general" action="" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p() }}
|
||||
<input class="form-submit-btn" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
|
||||
<h3>{% trans %}Trombi{% endtrans %}</h3>
|
||||
|
||||
{% if trombi_form %}
|
||||
<form class="form form-trombi" action="{{ url('trombi:user_tools') }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ trombi_form.as_p() }}
|
||||
<input class="form-submit-btn" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
<p>{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %}
|
||||
<br />
|
||||
<a href="{{ url('trombi:user_tools') }}">{% trans %}Go to my Trombi tools{% endtrans %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<h3>{% trans %}Notifications{% endtrans %}</h3>
|
||||
<form action="" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="form form-general">
|
||||
{{ form.as_p() }}
|
||||
</div>
|
||||
<input class="btn btn-blue" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
|
||||
<br />
|
||||
<h3>{% trans %}Visibility{% endtrans %}</h3>
|
||||
|
||||
{{ user_visibility_fragment }}
|
||||
|
||||
<br />
|
||||
{% if student_card_fragment %}
|
||||
<h3>{% trans %}Student card{% endtrans %}</h3>
|
||||
{{ student_card_fragment }}
|
||||
@@ -43,5 +42,21 @@
|
||||
add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<br />
|
||||
<h3>{% trans %}Trombi{% endtrans %}</h3>
|
||||
|
||||
{% if trombi_form %}
|
||||
<form action="{{ url('trombi:user_tools') }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ trombi_form.as_p() }}
|
||||
<input class="btn btn-blue" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %}
|
||||
<br />
|
||||
<a href="{{ url('trombi:user_tools') }}">{% trans %}Go to my Trombi tools{% endtrans %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -399,13 +399,12 @@ class TestUserQuerySetViewableBy:
|
||||
return [
|
||||
baker.make(User),
|
||||
subscriber_user.make(),
|
||||
subscriber_user.make(is_viewable=False),
|
||||
*subscriber_user.make(is_viewable=False, _quantity=2),
|
||||
]
|
||||
|
||||
def test_admin_user(self, users: list[User]):
|
||||
user = baker.make(
|
||||
User,
|
||||
user_permissions=[Permission.objects.get(codename="view_hidden_user")],
|
||||
User, user_permissions=[Permission.objects.get(codename="view_hidden_user")]
|
||||
)
|
||||
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
|
||||
assert set(viewable) == set(users)
|
||||
@@ -418,6 +417,12 @@ class TestUserQuerySetViewableBy:
|
||||
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
|
||||
assert set(viewable) == {users[0], users[1]}
|
||||
|
||||
def test_whitelist(self, users: list[User]):
|
||||
user = subscriber_user.make()
|
||||
users[3].whitelisted_users.add(user)
|
||||
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
|
||||
assert set(viewable) == {users[0], users[1], users[3]}
|
||||
|
||||
@pytest.mark.parametrize("user_factory", [lambda: baker.make(User), AnonymousUser])
|
||||
def test_not_subscriber(self, users: list[User], user_factory):
|
||||
user = user_factory()
|
||||
|
||||
@@ -69,7 +69,6 @@ from core.views import (
|
||||
UserCreationView,
|
||||
UserGodfathersTreeView,
|
||||
UserGodfathersView,
|
||||
UserListView,
|
||||
UserMeRedirect,
|
||||
UserMiniView,
|
||||
UserPreferencesView,
|
||||
@@ -78,6 +77,7 @@ from core.views import (
|
||||
UserUpdateGroupView,
|
||||
UserUpdateProfileView,
|
||||
UserView,
|
||||
UserVisibilityFormFragment,
|
||||
delete_user_godfather,
|
||||
logout,
|
||||
notification,
|
||||
@@ -136,7 +136,11 @@ urlpatterns = [
|
||||
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
|
||||
),
|
||||
# User views
|
||||
path("user/", UserListView.as_view(), name="user_list"),
|
||||
path(
|
||||
"fragment/user/<int:user_id>/",
|
||||
UserVisibilityFormFragment.as_view(),
|
||||
name="user_visibility_fragment",
|
||||
),
|
||||
path(
|
||||
"user/me/<path:remaining_path>/",
|
||||
UserMeRedirect.as_view(),
|
||||
|
||||
@@ -48,12 +48,13 @@ from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
||||
from PIL import Image
|
||||
|
||||
from antispam.forms import AntiSpamEmailField
|
||||
from core.models import Gift, Group, Page, PageRev, SithFile, User
|
||||
from core.models import Gift, Group, Page, PageRev, Preferences, SithFile, User
|
||||
from core.utils import resize_image
|
||||
from core.views.widgets.ajax_select import (
|
||||
AutoCompleteSelect,
|
||||
AutoCompleteSelectGroup,
|
||||
AutoCompleteSelectMultipleGroup,
|
||||
AutoCompleteSelectMultipleUser,
|
||||
AutoCompleteSelectUser,
|
||||
)
|
||||
from core.views.widgets.markdown import MarkdownInput
|
||||
@@ -179,7 +180,6 @@ class UserProfileForm(forms.ModelForm):
|
||||
"school",
|
||||
"promo",
|
||||
"forum_signature",
|
||||
"is_viewable",
|
||||
]
|
||||
widgets = {
|
||||
"date_of_birth": SelectDate,
|
||||
@@ -264,6 +264,38 @@ class UserProfileForm(forms.ModelForm):
|
||||
self._post_clean()
|
||||
|
||||
|
||||
class UserVisibilityForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["is_viewable", "whitelisted_users"]
|
||||
widgets = {
|
||||
"is_viewable": forms.CheckboxInput(attrs={"class": "switch"}),
|
||||
"whitelisted_users": AutoCompleteSelectMultipleUser,
|
||||
}
|
||||
|
||||
__preferences_fields = forms.fields_for_model(
|
||||
Preferences,
|
||||
["show_my_stats"],
|
||||
widgets={"show_my_stats": forms.CheckboxInput(attrs={"class": "switch"})},
|
||||
)
|
||||
show_my_stats = __preferences_fields["show_my_stats"]
|
||||
|
||||
def __init__(
|
||||
self, *args, initial: dict | None = None, instance: User | None = None, **kwargs
|
||||
):
|
||||
if instance:
|
||||
initial = initial or {}
|
||||
initial["show_my_stats"] = instance.preferences.show_my_stats
|
||||
super().__init__(*args, initial=initial, instance=instance, **kwargs)
|
||||
|
||||
def save(self, commit=True) -> User: # noqa: FBT002
|
||||
instance = super().save(commit=commit)
|
||||
if commit:
|
||||
instance.preferences.show_my_stats = self.cleaned_data["show_my_stats"]
|
||||
instance.preferences.save()
|
||||
return instance
|
||||
|
||||
|
||||
class UserGroupsForm(forms.ModelForm):
|
||||
error_css_class = "error"
|
||||
required_css_class = "required"
|
||||
|
||||
@@ -28,10 +28,12 @@ from datetime import timedelta
|
||||
from operator import itemgetter
|
||||
from smtplib import SMTPException
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import login, views
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import DateField, F, QuerySet, Sum
|
||||
from django.db.models.functions import Trunc
|
||||
@@ -48,7 +50,6 @@ from django.views.generic import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
DetailView,
|
||||
ListView,
|
||||
RedirectView,
|
||||
TemplateView,
|
||||
)
|
||||
@@ -65,8 +66,9 @@ from core.views.forms import (
|
||||
UserGodfathersForm,
|
||||
UserGroupsForm,
|
||||
UserProfileForm,
|
||||
UserVisibilityForm,
|
||||
)
|
||||
from core.views.mixins import TabedViewMixin, UseFragmentsMixin
|
||||
from core.views.mixins import FragmentMixin, TabedViewMixin, UseFragmentsMixin
|
||||
from counter.models import Refilling, Selling
|
||||
from eboutic.models import Invoice
|
||||
from trombi.views import UserTrombiForm
|
||||
@@ -248,14 +250,15 @@ class UserTabsMixin(TabedViewMixin):
|
||||
"name": _("Groups"),
|
||||
}
|
||||
)
|
||||
if (
|
||||
can_view_account = (
|
||||
hasattr(user, "customer")
|
||||
and user.customer
|
||||
and (
|
||||
user == self.request.user
|
||||
or self.request.user.has_perm("counter.view_customer")
|
||||
)
|
||||
):
|
||||
)
|
||||
if can_view_account or user.preferences.show_my_stats:
|
||||
tab_list.append(
|
||||
{
|
||||
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
||||
@@ -263,6 +266,7 @@ class UserTabsMixin(TabedViewMixin):
|
||||
"name": _("Stats"),
|
||||
}
|
||||
)
|
||||
if can_view_account:
|
||||
tab_list.append(
|
||||
{
|
||||
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
|
||||
@@ -349,7 +353,7 @@ class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView):
|
||||
return kwargs
|
||||
|
||||
|
||||
class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
|
||||
class UserStatsView(UserTabsMixin, UserPassesTestMixin, DetailView):
|
||||
"""Display a user's stats."""
|
||||
|
||||
model = User
|
||||
@@ -357,15 +361,20 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
|
||||
context_object_name = "profile"
|
||||
template_name = "core/user_stats.jinja"
|
||||
current_tab = "stats"
|
||||
queryset = User.objects.exclude(customer=None).select_related("customer")
|
||||
queryset = User.objects.exclude(customer=None).select_related(
|
||||
"customer", "_preferences"
|
||||
)
|
||||
|
||||
def dispatch(self, request, *arg, **kwargs):
|
||||
profile = self.get_object()
|
||||
if not (
|
||||
profile == request.user or request.user.has_perm("counter.view_customer")
|
||||
):
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *arg, **kwargs)
|
||||
def test_func(self):
|
||||
profile: User = self.get_object()
|
||||
return (
|
||||
profile == self.request.user
|
||||
or self.request.user.has_perm("counter.view_customer")
|
||||
or (
|
||||
self.request.user.can_view(profile)
|
||||
and profile.preferences.show_my_stats
|
||||
)
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
@@ -404,13 +413,6 @@ class UserMiniView(CanViewMixin, DetailView):
|
||||
template_name = "core/user_mini.jinja"
|
||||
|
||||
|
||||
class UserListView(ListView, CanEditPropMixin):
|
||||
"""Displays the user list."""
|
||||
|
||||
model = User
|
||||
template_name = "core/user_list.jinja"
|
||||
|
||||
|
||||
# FIXME: the edit_once fields aren't displayed to the user (as expected).
|
||||
# However, if the user re-add them manually in the form, they are saved.
|
||||
class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
|
||||
@@ -468,6 +470,30 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
|
||||
current_tab = "clubs"
|
||||
|
||||
|
||||
class UserVisibilityFormFragment(FragmentMixin, SuccessMessageMixin, UpdateView):
|
||||
model = User
|
||||
form_class = UserVisibilityForm
|
||||
template_name = "core/fragment/user_visibility.jinja"
|
||||
pk_url_kwarg = "user_id"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return super().get_form_kwargs() | {"label_suffix": ""}
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
messages.success(
|
||||
self.request, _("Visibility parameters updated."), extra_tags="visibility"
|
||||
)
|
||||
return response
|
||||
|
||||
def render_fragment(self, request, **kwargs) -> SafeString:
|
||||
self.object = kwargs.get("user")
|
||||
return super().render_fragment(request, **kwargs)
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
return self.request.path
|
||||
|
||||
|
||||
class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, UpdateView):
|
||||
"""Edit a user's preferences."""
|
||||
|
||||
@@ -481,7 +507,10 @@ class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, Update
|
||||
current_tab = "prefs"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return super().get_form_kwargs() | {"instance": self.object.preferences}
|
||||
return super().get_form_kwargs() | {
|
||||
"instance": self.object.preferences,
|
||||
"label_suffix": "",
|
||||
}
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.path
|
||||
@@ -491,6 +520,9 @@ class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, Update
|
||||
from counter.views.student_card import StudentCardFormFragment
|
||||
|
||||
res = super().get_fragment_context_data()
|
||||
res["user_visibility_fragment"] = UserVisibilityFormFragment.as_fragment()(
|
||||
self.request, user=self.object
|
||||
)
|
||||
if hasattr(self.object, "customer"):
|
||||
res["student_card_fragment"] = StudentCardFormFragment.as_fragment()(
|
||||
self.request, customer=self.object.customer
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from 'core/macros.jinja' import user_profile_link, paginate_jinja %}
|
||||
{% from 'core/macros.jinja' import user_profile_link, paginate_jinja with context %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}Cash register summary list{% endtrans %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from "core/macros.jinja" import paginate_jinja %}
|
||||
{% from "core/macros.jinja" import paginate_jinja with context %}
|
||||
|
||||
{% block title %}
|
||||
{%- trans %}Reloads list{% endtrans %} -- {{ counter.name }}
|
||||
|
||||
@@ -116,6 +116,56 @@
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<section>
|
||||
<div class="category-header">
|
||||
<h3 class="margin-bottom">{% trans %}Eurockéennes 2025 partnership{% endtrans %}</h3>
|
||||
{% if user.is_subscribed %}
|
||||
<div id="eurock-partner" style="
|
||||
min-height: 600px;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
">
|
||||
<p style="text-align: center;">
|
||||
{% trans trimmed %}
|
||||
Our partner uses Weezevent to sell tickets.
|
||||
Weezevent may collect user info according to
|
||||
its own privacy policy.
|
||||
By clicking the accept button you consent to
|
||||
their terms of services.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
|
||||
<a href="https://weezevent.com/fr/politique-de-confidentialite/">{% trans %}Privacy policy{% endtrans %}</a>
|
||||
|
||||
<button
|
||||
hx-get="{{ url("eboutic:eurock") }}"
|
||||
hx-target="#eurock-partner"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="click, load[document.cookie.includes('weezevent_accept=true')]"
|
||||
@htmx:after-request="document.cookie = 'weezevent_accept=true'"
|
||||
>{% trans %}Accept{% endtrans %}
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
{%- trans trimmed %}
|
||||
You must be subscribed to benefit from the partnership with the Eurockéennes.
|
||||
{% endtrans -%}
|
||||
</p>
|
||||
<p>
|
||||
{%- trans trimmed %}
|
||||
This partnership offers a discount of up to 33%
|
||||
on tickets for Friday, Saturday and Sunday,
|
||||
as well as the 3-day package from Friday to Sunday.
|
||||
{% endtrans -%}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% for priority_groups in products|groupby('order') %}
|
||||
{% for category, items in priority_groups.list|groupby('category') %}
|
||||
{% if items|count > 0 %}
|
||||
|
||||
16
eboutic/templates/eboutic/eurock_fragment.jinja
Normal file
16
eboutic/templates/eboutic/eurock_fragment.jinja
Normal file
@@ -0,0 +1,16 @@
|
||||
<a title="Logiciel billetterie en ligne"
|
||||
href="https://www.weezevent.com?c=sys_widget"
|
||||
class="weezevent-widget-integration"
|
||||
target="_blank"
|
||||
data-src="https://widget.weezevent.com/ticket/8aaba226-f7a3-4192-a64e-72ff8f5b35b7?id_evenement=1419869&locale=fr-FR&code=28747"
|
||||
data-width="650"
|
||||
data-height="600"
|
||||
data-resize="1"
|
||||
data-nopb="0"
|
||||
data-type="neo"
|
||||
data-width_auto="1"
|
||||
data-noscroll="0"
|
||||
data-id="1419869">
|
||||
Billetterie Weezevent
|
||||
</a>
|
||||
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<a
|
||||
title="Logiciel billetterie en ligne"
|
||||
href="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||
class="weezevent-widget-integration"
|
||||
target="_blank"
|
||||
data-src="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||
data-width="650"
|
||||
data-height="600"
|
||||
data-resize="1"
|
||||
data-nopb="0"
|
||||
data-type="neo"
|
||||
data-width_auto="1"
|
||||
data-noscroll="0"
|
||||
data-id="1211855">
|
||||
Billetterie Weezevent
|
||||
</a>
|
||||
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>
|
||||
@@ -31,6 +31,7 @@ from eboutic.views import (
|
||||
EbouticMainView,
|
||||
EbouticPayWithSith,
|
||||
EtransactionAutoAnswer,
|
||||
EurockPartnerFragment,
|
||||
payment_result,
|
||||
)
|
||||
|
||||
@@ -50,4 +51,5 @@ urlpatterns = [
|
||||
EtransactionAutoAnswer.as_view(),
|
||||
name="etransation_autoanswer",
|
||||
),
|
||||
path("eurock/", EurockPartnerFragment.as_view(), name="eurock"),
|
||||
]
|
||||
|
||||
@@ -42,11 +42,11 @@ from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.http import require_GET
|
||||
from django.views.generic import DetailView, FormView, UpdateView, View
|
||||
from django.views.generic import DetailView, FormView, TemplateView, UpdateView, View
|
||||
from django.views.generic.edit import SingleObjectMixin
|
||||
from django_countries.fields import Country
|
||||
|
||||
from core.auth.mixins import CanViewMixin
|
||||
from core.auth.mixins import CanViewMixin, IsSubscriberMixin
|
||||
from core.views.mixins import FragmentMixin, UseFragmentsMixin
|
||||
from counter.forms import BaseBasketForm, BasketProductForm, BillingInfoForm
|
||||
from counter.models import (
|
||||
@@ -350,3 +350,7 @@ class EtransactionAutoAnswer(View):
|
||||
return HttpResponse(
|
||||
"Payment failed with error: " + request.GET["Error"], status=202
|
||||
)
|
||||
|
||||
|
||||
class EurockPartnerFragment(IsSubscriberMixin, TemplateView):
|
||||
template_name = "eboutic/eurock_fragment.jinja"
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<label for="{{ input_id }}">
|
||||
{%- endif %}
|
||||
<figure>
|
||||
{%- if user.is_viewable %}
|
||||
{%- if user.can_view(candidature.user) %}
|
||||
{% if candidature.user.profile_pict %}
|
||||
<img class="candidate__picture" src="{{ candidature.user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}">
|
||||
{% else %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from "core/macros.jinja" import paginate_jinja %}
|
||||
{% from "core/macros.jinja" import paginate_jinja with context %}
|
||||
|
||||
{% block title %}
|
||||
{%- trans %}Election list{% endtrans %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from 'core/macros.jinja' import user_profile_link %}
|
||||
{% from 'forum/macros.jinja' import display_message, display_breadcrumb, display_search_bar %}
|
||||
{% from 'core/macros.jinja' import paginate_jinja %}
|
||||
{% from 'core/macros.jinja' import paginate_jinja with context %}
|
||||
|
||||
{% block title %}
|
||||
{{ topic }}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-10 10:28+0100\n"
|
||||
"POT-Creation-Date: 2026-03-23 22:21+0100\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"
|
||||
@@ -310,16 +310,36 @@ msgid "The list of all clubs existing at UTBM."
|
||||
msgstr "La liste de tous les clubs existants à l'UTBM"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "inactive"
|
||||
msgstr "inactif"
|
||||
msgid "Filters"
|
||||
msgstr "Filtres"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "Club state"
|
||||
msgstr "Etat du club"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "Active"
|
||||
msgstr "Actif"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "Inactive"
|
||||
msgstr "Inactif"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "All clubs"
|
||||
msgstr "Tous les clubs"
|
||||
|
||||
#: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja
|
||||
msgid "New club"
|
||||
msgstr "Nouveau club"
|
||||
|
||||
#: club/templates/club/club_list.jinja
|
||||
msgid "There is no club in this website."
|
||||
msgstr "Il n'y a pas de club dans ce site web."
|
||||
msgid "inactive"
|
||||
msgstr "inactif"
|
||||
|
||||
#: club/templates/club/club_members.jinja
|
||||
msgid "Club members"
|
||||
@@ -551,8 +571,9 @@ msgstr ""
|
||||
#: com/templates/com/news_edit.jinja com/templates/com/poster_edit.jinja
|
||||
#: com/templates/com/screen_edit.jinja com/templates/com/weekmail.jinja
|
||||
#: core/templates/core/create.jinja core/templates/core/edit.jinja
|
||||
#: core/templates/core/file_edit.jinja core/templates/core/page/edit.jinja
|
||||
#: core/templates/core/page/prop.jinja
|
||||
#: core/templates/core/file_edit.jinja
|
||||
#: core/templates/core/fragment/user_visibility.jinja
|
||||
#: core/templates/core/page/edit.jinja core/templates/core/page/prop.jinja
|
||||
#: core/templates/core/user_godfathers.jinja
|
||||
#: core/templates/core/user_godfathers_tree.jinja
|
||||
#: core/templates/core/user_preferences.jinja
|
||||
@@ -1547,6 +1568,18 @@ msgid ""
|
||||
msgstr ""
|
||||
"Si vous désactivez cette option, seuls les admins pourront voir votre profil."
|
||||
|
||||
#: core/models.py
|
||||
msgid "whitelisted users"
|
||||
msgstr "utilisateurs whitelistés"
|
||||
|
||||
#: core/models.py
|
||||
msgid ""
|
||||
"Even if this profile is hidden, the users in this list will still be able to "
|
||||
"see it."
|
||||
msgstr ""
|
||||
"Même si ce profil est caché, les utilisateurs sur cette liste pourront "
|
||||
"toujours le voir."
|
||||
|
||||
#: core/models.py
|
||||
msgid "A user with that username already exists"
|
||||
msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
|
||||
@@ -1603,6 +1636,14 @@ msgstr "recevoir le Weekmail"
|
||||
msgid "show your stats to others"
|
||||
msgstr "montrez vos statistiques aux autres"
|
||||
|
||||
#: core/models.py
|
||||
msgid ""
|
||||
"Allow subscribers (or whitelisted users if your profile is hidden) to access "
|
||||
"your AE account stats."
|
||||
msgstr ""
|
||||
"Autorise les cotisants (ou les personnes whitelistées, si votre profil est "
|
||||
"caché) à accéder aux statistiques de votre compte AE"
|
||||
|
||||
#: core/models.py
|
||||
msgid "get a notification for every click"
|
||||
msgstr "avoir une notification pour chaque click"
|
||||
@@ -1860,10 +1901,6 @@ msgstr "L'AE"
|
||||
msgid "AE's clubs"
|
||||
msgstr "Les clubs de L'AE"
|
||||
|
||||
#: core/templates/core/base/navbar.jinja
|
||||
msgid "Others UTBM's Associations"
|
||||
msgstr "Les autres associations de l'UTBM"
|
||||
|
||||
#: core/templates/core/base/navbar.jinja
|
||||
msgid "Big event"
|
||||
msgstr "Grandes Activités"
|
||||
@@ -2612,21 +2649,12 @@ msgid "Preferences"
|
||||
msgstr "Préférences"
|
||||
|
||||
#: core/templates/core/user_preferences.jinja
|
||||
msgid "General"
|
||||
msgstr "Général"
|
||||
|
||||
#: core/templates/core/user_preferences.jinja trombi/views.py
|
||||
msgid "Trombi"
|
||||
msgstr "Trombi"
|
||||
msgid "Notifications"
|
||||
msgstr "Notifications"
|
||||
|
||||
#: core/templates/core/user_preferences.jinja
|
||||
#, python-format
|
||||
msgid "You already choose to be in that Trombi: %(trombi)s."
|
||||
msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s."
|
||||
|
||||
#: core/templates/core/user_preferences.jinja
|
||||
msgid "Go to my Trombi tools"
|
||||
msgstr "Allez à mes outils de Trombi"
|
||||
msgid "Visibility"
|
||||
msgstr "Visibilité"
|
||||
|
||||
#: core/templates/core/user_preferences.jinja
|
||||
#: counter/templates/counter/counter_click.jinja
|
||||
@@ -2645,6 +2673,19 @@ msgstr ""
|
||||
"aurez besoin d'un lecteur NFC. Nous enregistrons l'UID de la carte qui fait "
|
||||
"14 caractères de long."
|
||||
|
||||
#: core/templates/core/user_preferences.jinja trombi/views.py
|
||||
msgid "Trombi"
|
||||
msgstr "Trombi"
|
||||
|
||||
#: core/templates/core/user_preferences.jinja
|
||||
#, python-format
|
||||
msgid "You already choose to be in that Trombi: %(trombi)s."
|
||||
msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s."
|
||||
|
||||
#: core/templates/core/user_preferences.jinja
|
||||
msgid "Go to my Trombi tools"
|
||||
msgstr "Allez à mes outils de Trombi"
|
||||
|
||||
#: core/templates/core/user_stats.jinja
|
||||
#, python-format
|
||||
msgid "%(user_name)s's stats"
|
||||
@@ -2925,6 +2966,10 @@ msgstr "Photos"
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#: core/views/user.py
|
||||
msgid "Visibility parameters updated."
|
||||
msgstr "Paramètres de visibilité mis à jour."
|
||||
|
||||
#: counter/apps.py counter/models.py
|
||||
msgid "counter"
|
||||
msgstr "comptoir"
|
||||
@@ -5838,3 +5883,39 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
|
||||
#, python-format
|
||||
msgid "Maximum characters: %(max_length)s"
|
||||
msgstr "Nombre de caractères max: %(max_length)s"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid "Eurockéennes 2025 partnership"
|
||||
msgstr "Partenariat Eurockéennes 2025"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid ""
|
||||
"Our partner uses Weezevent to sell tickets. Weezevent may collect user info "
|
||||
"according to its own privacy policy. By clicking the accept button you "
|
||||
"consent to their terms of services."
|
||||
msgstr ""
|
||||
"Notre partenaire utilises Wezevent pour vendre ses billets. Weezevent peut "
|
||||
"collecter des informations utilisateur conformément à sa propre politique de "
|
||||
"confidentialité. En cliquant sur le bouton d'acceptation vous consentez à "
|
||||
"leurs termes de service."
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid "Privacy policy"
|
||||
msgstr "Politique de confidentialité"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
msgid ""
|
||||
"You must be subscribed to benefit from the partnership with the Eurockéennes."
|
||||
msgstr ""
|
||||
"Vous devez être cotisant pour bénéficier du partenariat avec les "
|
||||
"Eurockéennes."
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This partnership offers a discount of up to 33%% on tickets for Friday, "
|
||||
"Saturday and Sunday, as well as the 3-day package from Friday to Sunday."
|
||||
msgstr ""
|
||||
"Ce partenariat permet de profiter d'une réduction jusqu'à 33%% sur les "
|
||||
"billets du vendredi, du samedi et du dimanche, ainsi qu'au forfait 3 jours, "
|
||||
"du vendredi au dimanche."
|
||||
|
||||
2047
package-lock.json
generated
2047
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,6 @@
|
||||
"compile-dev": "vite build --mode development",
|
||||
"serve": "vite build --mode development --watch --minify false",
|
||||
"openapi": "openapi-ts",
|
||||
"analyse-dev": "vite-bundle-visualizer --mode development",
|
||||
"analyse-prod": "vite-bundle-visualizer --mode production",
|
||||
"check": "tsc && biome check --write"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -35,10 +33,9 @@
|
||||
"@types/cytoscape-cxtmenu": "^3.4.5",
|
||||
"@types/cytoscape-klay": "^3.1.5",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-bundle-visualizer": "^1.2.1",
|
||||
"vite-plugin-static-copy": "^3.2.0"
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alpinejs/sort": "^3.15.8",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from "core/macros.jinja" import paginate_jinja %}
|
||||
{% from "core/macros.jinja" import paginate_jinja with context %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}Operation logs{% endtrans %}
|
||||
|
||||
@@ -270,7 +270,11 @@ class PeoplePictureRelationQuerySet(models.QuerySet):
|
||||
if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID):
|
||||
return self
|
||||
if user.was_subscribed:
|
||||
return self.filter(Q(user_id=user.id) | Q(user__is_viewable=True))
|
||||
return self.filter(
|
||||
Q(user_id=user.id)
|
||||
| Q(user__is_viewable=True)
|
||||
| Q(user__whitelisted_users=user)
|
||||
)
|
||||
return self.filter(user_id=user.id)
|
||||
|
||||
|
||||
|
||||
@@ -355,7 +355,6 @@ SITH_TWITTER = "@ae_utbm"
|
||||
# AE configuration
|
||||
SITH_MAIN_CLUB_ID = env.int("SITH_MAIN_CLUB_ID", default=1)
|
||||
SITH_PDF_CLUB_ID = env.int("SITH_PDF_CLUB_ID", default=2)
|
||||
SITH_LAUNDERETTE_CLUB_ID = env.int("SITH_LAUNDERETTE_CLUB_ID", default=84)
|
||||
|
||||
# Main root for club pages
|
||||
SITH_CLUB_ROOT_PAGE = "clubs"
|
||||
@@ -483,13 +482,6 @@ SITH_LOG_OPERATION_TYPE = [
|
||||
|
||||
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
|
||||
|
||||
SITH_ECOCUP_CONS = env.int("SITH_ECOCUP_CONS", default=1151)
|
||||
|
||||
SITH_ECOCUP_DECO = env.int("SITH_ECOCUP_DECO", default=1152)
|
||||
|
||||
# The limit is the maximum difference between cons and deco possible for a customer
|
||||
SITH_ECOCUP_LIMIT = 3
|
||||
|
||||
# Defines pagination for cash summary
|
||||
SITH_COUNTER_CASH_SUMMARY_LENGTH = 50
|
||||
|
||||
@@ -512,7 +504,6 @@ SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = env.int(
|
||||
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = env.int(
|
||||
"SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS", default=2
|
||||
)
|
||||
SITH_PRODUCTTYPE_SUBSCRIPTION = env.int("SITH_PRODUCTTYPE_SUBSCRIPTION", default=2)
|
||||
|
||||
# Number of weeks before the end of a subscription when the subscriber can resubscribe
|
||||
SITH_SUBSCRIPTION_END = 10
|
||||
|
||||
89
uv.lock
generated
89
uv.lock
generated
@@ -423,55 +423,55 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.6"
|
||||
version = "46.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -817,7 +817,6 @@ wheels = [
|
||||
name = "griffelib"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/06/eccbd311c9e2b3ca45dbc063b93134c57a1ccc7607c5e545264ad092c4a9/griffelib-2.0.0.tar.gz", hash = "sha256:e504d637a089f5cab9b5daf18f7645970509bf4f53eda8d79ed71cce8bd97934", size = 166312, upload-time = "2026-03-23T21:06:55.954Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" },
|
||||
]
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
// biome-ignore lint/correctness/noNodejsModules: this is backend side
|
||||
import { parse, resolve } from "node:path";
|
||||
import inject from "@rollup/plugin-inject";
|
||||
import { glob } from "glob";
|
||||
import type { Rollup } from "vite";
|
||||
import { type AliasOptions, defineConfig, type UserConfig } from "vite";
|
||||
import { visualizer } from "rollup-plugin-visualizer";
|
||||
import {
|
||||
type AliasOptions,
|
||||
defineConfig,
|
||||
type PluginOption,
|
||||
type Rollup,
|
||||
type UserConfig,
|
||||
} from "vite";
|
||||
import tsconfig from "./tsconfig.json";
|
||||
|
||||
const outDir = resolve(__dirname, "./staticfiles/generated/bundled");
|
||||
const vendored = resolve(outDir, "vendored");
|
||||
const nodeModules = resolve(__dirname, "node_modules");
|
||||
const collectedFiles = glob.sync(
|
||||
"./!(static)/static/bundled/**/*?(-)index.?(m)[j|t]s?(x)",
|
||||
);
|
||||
@@ -42,7 +45,6 @@ function getRelativeAssetPath(path: string): string {
|
||||
return relativePath.join("/");
|
||||
}
|
||||
|
||||
// biome-ignore lint/style/noDefaultExport: this is recommended by documentation
|
||||
export default defineConfig((config: UserConfig) => {
|
||||
return {
|
||||
base: "/static/bundled/",
|
||||
@@ -86,6 +88,7 @@ export default defineConfig((config: UserConfig) => {
|
||||
Alpine: "alpinejs",
|
||||
htmx: "htmx.org",
|
||||
}),
|
||||
visualizer({ filename: ".bundle-size-report.html" }) as PluginOption,
|
||||
],
|
||||
} satisfies UserConfig;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user