2 Commits

Author SHA1 Message Date
TitouanDor
623e3cdbff correction fautes orthographes 2026-03-17 15:04:15 +01:00
TitouanDor
d11b32ef2d add button for general term and condition 2026-03-17 15:04:15 +01:00
28 changed files with 195 additions and 466 deletions

View File

@@ -244,8 +244,9 @@ class NewsListView(TemplateView):
.filter(
date_of_birth__month=localdate().month,
date_of_birth__day=localdate().day,
role__in=["STUDENT", "FORMER STUDENT"],
is_viewable=True,
)
.filter(role__in=["STUDENT", "FORMER STUDENT"])
.order_by("-date_of_birth"),
key=lambda u: u.date_of_birth.year,
)

View File

@@ -63,7 +63,6 @@ class UserAdmin(admin.ModelAdmin):
"scrub_pict",
"user_permissions",
"groups",
"whitelisted_users",
)
inlines = (UserBanInline,)
search_fields = ["first_name", "last_name", "username"]

View File

@@ -116,11 +116,7 @@ class Command(BaseCommand):
)
main_club.board_group.permissions.add(
*Permission.objects.filter(
codename__in=[
"view_subscription",
"add_subscription",
"view_hidden_user",
]
codename__in=["view_subscription", "add_subscription"]
)
)
bar_club = Club.objects.create(

View File

@@ -1,37 +0,0 @@
# 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",
),
),
]

View File

@@ -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(Q(is_viewable=True) | Q(whitelisted_users=user))
return self.filter(is_viewable=True)
if user.is_anonymous:
return self.none()
return self.filter(id=user.id)
@@ -279,16 +279,6 @@ 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()
@@ -528,7 +518,7 @@ class User(AbstractUser):
self.username = user_name
return user_name
def is_owner(self, obj: models.Model):
def is_owner(self, obj):
"""Determine if the object is owned by the user."""
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
return True
@@ -536,7 +526,7 @@ class User(AbstractUser):
return True
return self.is_root
def can_edit(self, obj: models.Model):
def can_edit(self, obj):
"""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
@@ -550,9 +540,11 @@ 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: models.Model):
def can_view(self, obj):
"""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
@@ -571,35 +563,14 @@ class User(AbstractUser):
return True
return self.can_edit(obj)
def can_be_edited_by(self, user: User):
return user == self or user.is_root or user.is_board_member
def can_be_edited_by(self, user):
return 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 is_in_whitelist(user))
)
or (user.has_perm("core.view_user") and self.is_viewable)
)
def get_mini_item(self):
@@ -779,14 +750,7 @@ 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"),
help_text=_(
"Allow subscribers (or whitelisted users "
"if your profile is hidden) to access your AE account stats."
),
default=False,
)
show_my_stats = models.BooleanField(_("show your stats to others"), default=False)
notify_on_click = models.BooleanField(
_("get a notification for every click"), default=False
)

View File

@@ -6,36 +6,14 @@ import { inheritHtmlElement, registerComponent } from "#core:utils/web-component
**/
@registerComponent("link-once")
export class LinkOnce extends inheritHtmlElement("link") {
refresh() {
this.clearNode();
connectedCallback() {
super.connectedCallback(false);
// We get href from node.attributes instead of node.href to avoid getting the domain part
const href = this.node.attributes.getNamedItem("href").nodeValue;
if (document.querySelectorAll(`link[href='${href}']`).length === 0) {
this.appendChild(this.node);
}
}
clearNode() {
while (this.firstChild) {
this.removeChild(this.lastChild);
}
}
connectedCallback() {
super.connectedCallback(false);
this.refresh();
}
disconnectedCallback() {
this.clearNode();
// This re-triggers link-once elements that still exists and suppressed
// themeselves once it gets removed from the page
for (const link of document.getElementsByTagName("link-once")) {
(link as LinkOnce).refresh();
}
}
}
/**
@@ -44,34 +22,12 @@ export class LinkOnce extends inheritHtmlElement("link") {
**/
@registerComponent("script-once")
export class ScriptOnce extends inheritHtmlElement("script") {
refresh() {
this.clearNode();
connectedCallback() {
super.connectedCallback(false);
// We get src from node.attributes instead of node.src to avoid getting the domain part
const src = this.node.attributes.getNamedItem("src").nodeValue;
if (document.querySelectorAll(`script[src='${src}']`).length === 0) {
this.appendChild(this.node);
}
}
clearNode() {
while (this.firstChild) {
this.removeChild(this.lastChild);
}
}
connectedCallback() {
super.connectedCallback(false);
this.refresh();
}
disconnectedCallback() {
this.clearNode();
// This re-triggers script-once elements that still exists and suppressed
// themeselves once it gets removed from the page
for (const link of document.getElementsByTagName("script-once")) {
(link as LinkOnce).refresh();
}
}
}

View File

@@ -157,7 +157,6 @@ form {
margin-bottom: .25rem;
font-size: 80%;
display: block;
max-width: calc(100% - calc(var(--nf-input-size) * 2))
}
fieldset {

View File

@@ -5,6 +5,17 @@
}
.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;

View File

@@ -19,6 +19,28 @@
}
}
}
&-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 {

View File

@@ -1,11 +1,14 @@
<div id="quick-notifications"
x-data="{
messages: [
{%- for message in messages -%}
{%- if not message.extra_tags -%}
{ tag: '{{ message.tags }}', text: '{{ message }}' },
{%- endif -%}
{%- endfor -%}
{% if messages %}
{% for message in messages %}
{
tag: '{{ message.tags }}',
text: '{{ message }}',
},
{% endfor %}
{% endif %}
]
}"
@quick-notification-add="(e) => messages.push(e?.detail)"

View File

@@ -1,33 +0,0 @@
<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>

View File

@@ -147,7 +147,18 @@
{%- 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>
@@ -159,6 +170,7 @@
</a>
</p>
{%- endif -%}
<p>
<input type="submit" value="{%- trans -%}Update{%- endtrans -%}" />
</p>

View File

@@ -11,22 +11,30 @@
{% block content %}
<div class="main">
<h2>{% trans %}Preferences{% endtrans %}</h2>
<br />
<h3>{% trans %}Notifications{% endtrans %}</h3>
<form action="" method="post" enctype="multipart/form-data">
<h3>{% trans %}General{% endtrans %}</h3>
<form class="form form-general" 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.as_p() }}
<input class="form-submit-btn" type="submit" value="{% trans %}Save{% endtrans %}" />
</form>
<br />
<h3>{% trans %}Visibility{% endtrans %}</h3>
<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 %}
{{ user_visibility_fragment }}
<br />
{% if student_card_fragment %}
<h3>{% trans %}Student card{% endtrans %}</h3>
{{ student_card_fragment }}
@@ -35,21 +43,5 @@
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 %}

View File

@@ -399,12 +399,13 @@ class TestUserQuerySetViewableBy:
return [
baker.make(User),
subscriber_user.make(),
*subscriber_user.make(is_viewable=False, _quantity=2),
subscriber_user.make(is_viewable=False),
]
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)
@@ -417,12 +418,6 @@ 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()

View File

@@ -69,6 +69,7 @@ from core.views import (
UserCreationView,
UserGodfathersTreeView,
UserGodfathersView,
UserListView,
UserMeRedirect,
UserMiniView,
UserPreferencesView,
@@ -77,7 +78,6 @@ from core.views import (
UserUpdateGroupView,
UserUpdateProfileView,
UserView,
UserVisibilityFormFragment,
delete_user_godfather,
logout,
notification,
@@ -136,11 +136,7 @@ urlpatterns = [
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
),
# User views
path(
"fragment/user/<int:user_id>/",
UserVisibilityFormFragment.as_view(),
name="user_visibility_fragment",
),
path("user/", UserListView.as_view(), name="user_list"),
path(
"user/me/<path:remaining_path>/",
UserMeRedirect.as_view(),

View File

@@ -48,13 +48,12 @@ from phonenumber_field.widgets import RegionalPhoneNumberWidget
from PIL import Image
from antispam.forms import AntiSpamEmailField
from core.models import Gift, Group, Page, PageRev, Preferences, SithFile, User
from core.models import Gift, Group, Page, PageRev, 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
@@ -180,6 +179,7 @@ class UserProfileForm(forms.ModelForm):
"school",
"promo",
"forum_signature",
"is_viewable",
]
widgets = {
"date_of_birth": SelectDate,
@@ -264,38 +264,6 @@ 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"

View File

@@ -28,12 +28,10 @@ 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
@@ -50,6 +48,7 @@ from django.views.generic import (
CreateView,
DeleteView,
DetailView,
ListView,
RedirectView,
TemplateView,
)
@@ -66,9 +65,8 @@ from core.views.forms import (
UserGodfathersForm,
UserGroupsForm,
UserProfileForm,
UserVisibilityForm,
)
from core.views.mixins import FragmentMixin, TabedViewMixin, UseFragmentsMixin
from core.views.mixins import TabedViewMixin, UseFragmentsMixin
from counter.models import Refilling, Selling
from eboutic.models import Invoice
from trombi.views import UserTrombiForm
@@ -250,15 +248,14 @@ class UserTabsMixin(TabedViewMixin):
"name": _("Groups"),
}
)
can_view_account = (
if (
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}),
@@ -266,7 +263,6 @@ class UserTabsMixin(TabedViewMixin):
"name": _("Stats"),
}
)
if can_view_account:
tab_list.append(
{
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
@@ -353,7 +349,7 @@ class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView):
return kwargs
class UserStatsView(UserTabsMixin, UserPassesTestMixin, DetailView):
class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
"""Display a user's stats."""
model = User
@@ -361,20 +357,15 @@ class UserStatsView(UserTabsMixin, UserPassesTestMixin, DetailView):
context_object_name = "profile"
template_name = "core/user_stats.jinja"
current_tab = "stats"
queryset = User.objects.exclude(customer=None).select_related(
"customer", "_preferences"
)
queryset = User.objects.exclude(customer=None).select_related("customer")
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 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 get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
@@ -413,6 +404,13 @@ 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):
@@ -470,30 +468,6 @@ 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."""
@@ -507,10 +481,7 @@ class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, Update
current_tab = "prefs"
def get_form_kwargs(self):
return super().get_form_kwargs() | {
"instance": self.object.preferences,
"label_suffix": "",
}
return super().get_form_kwargs() | {"instance": self.object.preferences}
def get_success_url(self):
return self.request.path
@@ -520,9 +491,6 @@ 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

View File

@@ -68,6 +68,12 @@
<template x-for="[key, value] in Object.entries(data)" :key="key">
<input type="hidden" :name="key" :value="value">
</template>
<input type="checkbox" value="cgv" required>
<label>
{% trans trimmed %}I have read and accept {% endtrans %}
<a href="">{% trans %}the general terms and conditions{% endtrans%}</a>
{%trans%}of the student assosiation of UTBM{% endtrans %}</label>
<br>
<input
x-cloak
type="submit"
@@ -93,6 +99,12 @@
{% else %}
<form method="post" action="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}" name="sith-pay-form">
{% csrf_token %}
<input type="checkbox" value="cgv" required>
<label>
{% trans trimmed %}I have read and accept {% endtrans %}
<a href="">{% trans %}the general terms and conditions{% endtrans%}</a>
{%trans%}of the student assosiation of UTBM{% endtrans %}</label>
<br>
<input class="btn btn-blue" type="submit" value="{% trans %}Pay with Sith account{% endtrans %}"/>
</form>
{% endif %}

View File

@@ -116,56 +116,6 @@
</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 %}

View File

@@ -1,16 +0,0 @@
<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>

View File

@@ -0,0 +1,17 @@
<a
title="Logiciel billetterie en ligne"
href="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
class="weezevent-widget-integration"
target="_blank"
data-src="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
data-width="650"
data-height="600"
data-resize="1"
data-nopb="0"
data-type="neo"
data-width_auto="1"
data-noscroll="0"
data-id="1211855">
Billetterie Weezevent
</a>
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>

View File

@@ -31,7 +31,6 @@ from eboutic.views import (
EbouticMainView,
EbouticPayWithSith,
EtransactionAutoAnswer,
EurockPartnerFragment,
payment_result,
)
@@ -51,5 +50,4 @@ urlpatterns = [
EtransactionAutoAnswer.as_view(),
name="etransation_autoanswer",
),
path("eurock/", EurockPartnerFragment.as_view(), name="eurock"),
]

View File

@@ -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, TemplateView, UpdateView, View
from django.views.generic import DetailView, FormView, UpdateView, View
from django.views.generic.edit import SingleObjectMixin
from django_countries.fields import Country
from core.auth.mixins import CanViewMixin, IsSubscriberMixin
from core.auth.mixins import CanViewMixin
from core.views.mixins import FragmentMixin, UseFragmentsMixin
from counter.forms import BaseBasketForm, BasketProductForm, BillingInfoForm
from counter.models import (
@@ -350,7 +350,3 @@ class EtransactionAutoAnswer(View):
return HttpResponse(
"Payment failed with error: " + request.GET["Error"], status=202
)
class EurockPartnerFragment(IsSubscriberMixin, TemplateView):
template_name = "eboutic/eurock_fragment.jinja"

View File

@@ -146,7 +146,7 @@
<label for="{{ input_id }}">
{%- endif %}
<figure>
{%- if user.can_view(candidature.user) %}
{%- if user.is_viewable %}
{% if candidature.user.profile_pict %}
<img class="candidate__picture" src="{{ candidature.user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}">
{% else %}

View File

@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-23 22:21+0100\n"
"POT-Creation-Date: 2026-03-10 10:28+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"
@@ -551,9 +551,8 @@ 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/fragment/user_visibility.jinja
#: core/templates/core/page/edit.jinja core/templates/core/page/prop.jinja
#: core/templates/core/file_edit.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
@@ -1548,18 +1547,6 @@ 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à"
@@ -1616,14 +1603,6 @@ 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"
@@ -2633,12 +2612,21 @@ msgid "Preferences"
msgstr "Préférences"
#: core/templates/core/user_preferences.jinja
msgid "Notifications"
msgstr "Notifications"
msgid "General"
msgstr "Général"
#: core/templates/core/user_preferences.jinja trombi/views.py
msgid "Trombi"
msgstr "Trombi"
#: core/templates/core/user_preferences.jinja
msgid "Visibility"
msgstr "Visibilité"
#, 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_preferences.jinja
#: counter/templates/counter/counter_click.jinja
@@ -2657,19 +2645,6 @@ 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"
@@ -2950,10 +2925,6 @@ 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"
@@ -4243,6 +4214,18 @@ msgstr ""
msgid "You can't buy a refilling with sith money"
msgstr "Vous ne pouvez pas acheter un rechargement avec de l'argent du sith"
#: eboutic/views.py
msgid "I have read and accept"
msgstr "J'ai lu et j'accepte"
#: eboutic/views.py
msgid "the general terms and conditions"
msgstr "les conditions générales de vente"
#: eboutic/views.py
msgid "of the student assosiation of UTBM"
msgstr "de l'Association des étudiants de l'UTBM"
#: election/forms.py
msgid "You have selected too many candidates."
msgstr "Vous avez sélectionné trop de candidats."
@@ -5867,39 +5850,3 @@ 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."

View File

@@ -270,11 +270,7 @@ 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)
| Q(user__whitelisted_users=user)
)
return self.filter(Q(user_id=user.id) | Q(user__is_viewable=True))
return self.filter(user_id=user.id)

View File

@@ -355,6 +355,7 @@ 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"
@@ -482,6 +483,13 @@ 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
@@ -504,6 +512,7 @@ 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

View File

@@ -6,8 +6,14 @@
{% trans %}New subscription{% endtrans %}
{% endblock %}
{# The following statics are bundled with our autocomplete select.
However, if one tries to swap a form by another, then the urls in script-once
and link-once disappear.
So we give them here.
If the aforementioned bug is resolved, you can remove this. #}
{% block additional_js %}
<script type="module" src="{{ static('bundled/core/components/tabs-index.ts') }}"></script>
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
<script
type="module"
src="{{ static("bundled/subscription/creation-form-existing-user-index.ts") }}"
@@ -15,6 +21,8 @@
{% endblock %}
{% block additional_css %}
<link rel="stylesheet" href="{{ static("core/components/tabs.scss") }}">
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
<link rel="stylesheet" href="{{ static("subscription/css/subscription.scss") }}">
{% endblock %}