Compare commits

..

9 Commits

Author SHA1 Message Date
TitouanDor 494367b743 ajout translation 2026-05-30 20:15:09 +02:00
TitouanDor edda337d6d modification traduction 2026-05-30 18:02:19 +02:00
TitouanDor 631cfd7fdc ajout lien vers la page des CGV 2026-05-30 18:02:19 +02:00
imperosol 2ee53d201e one CGV button for and etransaction and sith account 2026-05-30 18:02:19 +02:00
TitouanDor b5332e95c7 ajout balise de traduction 2026-05-30 18:02:19 +02:00
TitouanDor c5d04f6f1d supression alert() 2026-05-30 18:02:19 +02:00
TitouanDor 530c9effb2 Juste one CGU checkbox 2026-05-30 18:02:19 +02:00
TitouanDor 4c04f84a25 correction fautes orthographes 2026-05-30 18:02:02 +02:00
TitouanDor 9ad269035d add button for general term and condition 2026-05-30 18:01:47 +02:00
11 changed files with 83 additions and 141 deletions
-24
View File
@@ -392,30 +392,6 @@ class ClubRoleForm(forms.ModelForm):
self.instance.order = cleaned_data["ORDER"] - 1
return cleaned_data
def save(self, commit=True): # noqa: FBT002
instance: ClubRole = super().save(commit=commit)
if commit and "is_board" in self.changed_data:
# if the role was moved from board to simple member,
# remove all users with that role from the club board group.
# If the role became a board role, add users with
# that role to the club board group.
group_id = instance.club.board_group_id
if self.cleaned_data["is_board"]:
User.groups.through.objects.bulk_create(
[
User.groups.through(user_id=u, group_id=group_id)
for u in Membership.objects.ongoing()
.filter(role=instance)
.values_list("user_id", flat=True)
],
ignore_conflicts=True,
)
else:
User.groups.through.objects.filter(
user__memberships__role=instance, group_id=group_id
).delete()
return instance
class ClubRoleCreateForm(forms.ModelForm):
"""Form to create a club role.
+1 -28
View File
@@ -4,7 +4,6 @@ import pytest
from django.contrib.auth.models import Permission
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.timezone import now
from model_bakery import baker, seq
from model_bakery.recipe import Recipe
from pytest_django.asserts import assertRedirects
@@ -240,7 +239,7 @@ class TestClubRoleUpdate(TestCase):
def test_president_moves_itself_out_of_the_presidency(self):
"""Test that if the user moves its own role out of the presidency,
then it loses access to the update page."""
then it's redirected to another page and loses access to the update page."""
self.payload["roles-0-is_presidency"] = False
self.client.force_login(self.user)
res = self.client.post(self.url, data=self.payload)
@@ -252,29 +251,3 @@ class TestClubRoleUpdate(TestCase):
res = self.client.get(self.url)
assert res.status_code == 403
def test_role_stops_being_board(self):
"""Test that if a role stops being a board role,
its users lose the club board group."""
self.payload["roles-0-is_board"] = False
self.payload["roles-0-is_presidency"] = False
self.payload["roles-1-is_board"] = False
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
assert formset.is_valid()
formset.save()
assert not self.user.groups.contains(self.club.board_group)
def test_role_becomes_board(self):
"""Test that if a role becomes a board role,
its active users get the club board group"""
members = [
baker.make(Membership, club=self.club, role=self.roles[0], end_date=None),
baker.make(Membership, club=self.club, role=self.roles[0], end_date=now()),
]
self.payload["roles-2-is_board"] = True
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
assert formset.is_valid()
formset.save()
# the second membership is finished, so its user shouldn't get the role
assert members[0].user.groups.contains(self.club.board_group)
assert not members[1].user.groups.contains(self.club.board_group)
-4
View File
@@ -46,10 +46,6 @@ details.accordion>.accordion-content {
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
overflow: hidden;
@media screen and (max-width: 600px) {
padding: .75em 1.5em;
}
}
@mixin animation($selector) {
-1
View File
@@ -141,7 +141,6 @@ form {
display: block;
margin: calc(var(--nf-input-size) * 1.5) auto 10px;
line-height: 1;
white-space: nowrap;
.fields-centered {
padding: 10px 10px 0;
@@ -1,6 +1,6 @@
import type { RecursivePartial, TomSettings } from "tom-select/src/types";
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base";
import { registerComponent } from "#core:utils/web-components";
import type { RecursivePartial, TomSettings } from "tom-select/dist/types/types";
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base.ts";
import { registerComponent } from "#core:utils/web-components.ts";
const productParsingRegex = /^(\d+x)?(.*)/i;
const codeParsingRegex = / \((\w+)\)$/;
@@ -63,6 +63,13 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
);
},
);
this.widget.hook("after", "onOptionSelect", () => {
/* Focus the next element if it's an input */
if (this.nextElementSibling.nodeName === "INPUT") {
(this.nextElementSibling as HTMLInputElement).focus();
}
});
}
protected tomSelectSettings(): RecursivePartial<TomSettings> {
/* We disable the dropdown on focus because we're going to always autofocus the widget */
@@ -73,7 +80,9 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
// We need to manually set weights or it results on an inconsistent
// behavior between production and development environment
searchField: [
// @ts-expect-error documentation says it's fine, specified type is wrong
{ field: "code", weight: 2 },
// @ts-expect-error documentation says it's fine, specified type is wrong
{ field: "text", weight: 0.5 },
],
};
@@ -25,9 +25,6 @@ document.addEventListener("alpine:init", () => {
}
this.codeField = this.$refs.codeField;
this.codeField.widget.hook("after", "onOptionSelect", () => {
this.handleCode();
});
this.codeField.widget.focus();
// It's quite tricky to manually apply attributes to the management part
@@ -157,7 +154,6 @@ document.addEventListener("alpine:init", () => {
this.addToBasket(code, quantity);
}
this.codeField.widget.clear();
this.codeField.widget.setTextboxValue("");
this.codeField.widget.focus();
},
}));
+1 -22
View File
@@ -42,28 +42,7 @@
min-width: 350px;
ul {
list-style: none;
display: flex;
flex-direction: column;
gap: .5rem;
margin-left: 0;
.basket-row {
display: flex;
align-items: center;
gap: 1rem;
.product-name {
flex: 1 2 0;
min-width: 0;
text-wrap: wrap;
}
}
}
form {
margin-top: .5rem;
margin-bottom: .5rem;
list-style-type: none;
}
}
+18 -27
View File
@@ -56,15 +56,10 @@
<div class="accordion-content">
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %}
<form method="post" action="" @submit.prevent="handleCode">
<form method="post" action=""
class="code_form" @submit.prevent="handleCode">
<counter-product-select
name="code"
x-ref="codeField"
autofocus
required
placeholder="{% trans %}Select a product...{% endtrans %}"
>
<counter-product-select name="code" x-ref="codeField" autofocus required placeholder="{% trans %}Select a product...{% endtrans %}">
<option value=""></option>
<optgroup label="{% trans %}Operations{% endtrans %}">
<option value="FIN">{% trans %}Confirm (FIN){% endtrans %}</option>
@@ -73,11 +68,13 @@
{%- for category, prices in categories.items() -%}
<optgroup label="{{ category }}">
{%- for price in prices -%}
<option value="{{ price.id }}">{{ price.full_label }} ({{ price.product.code }})</option>
<option value="{{ price.id }}">{{ price.full_label }}</option>
{%- endfor -%}
</optgroup>
{%- endfor -%}
</counter-product-select>
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>
{% for error in form.non_form_errors() %}
@@ -105,9 +102,7 @@
{{ form.management_form }}
</div>
<ul>
<li x-show="getBasketSize() === 0">
<em>{% trans %}This basket is empty{% endtrans %}</em>
</li>
<li x-show="getBasketSize() === 0">{% trans %}This basket is empty{% endtrans %}</li>
<template x-for="(item, index) in Object.values(basket)" :key="item.product.price.id">
<li>
<template x-for="error in item.errors">
@@ -115,23 +110,19 @@
</div>
</template>
<div class="basket-row">
<div>
<button @click.prevent="addToBasket(item.product.price.id, -1)">-</button>
<span class="quantity" x-text="item.quantity"></span>
<button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
</div>
<button @click.prevent="addToBasket(item.product.price.id, -1)">-</button>
<span class="quantity" x-text="item.quantity"></span>
<button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
<span class="product-name" x-text="item.product.name"></span>
<span x-text="`${item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })} €`"></span>
<span x-show="item.getBonusQuantity() > 0"
x-text="`${item.getBonusQuantity()} x P`"></span>
<span x-text="item.product.name"></span> :
<span x-text="item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })"></span>
<span x-show="item.getBonusQuantity() > 0"
x-text="`${item.getBonusQuantity()} x P`"></span>
<button
class="remove-item"
@click.prevent="removeFromBasket(item.product.price.id)"
><i class="fa fa-trash-can delete-action"></i></button>
</div>
<button
class="remove-item"
@click.prevent="removeFromBasket(item.product.price.id)"
><i class="fa fa-trash-can delete-action"></i></button>
<input
type="hidden"
@@ -24,8 +24,8 @@
<thead>
<tr>
<td>Article</td>
<td>Quantity</td>
<td>Unit price</td>
<td>{% trans %}Quantity{% endtrans %}</td>
<td>{% trans %}Unit price{% endtrans %}</td>
</tr>
</thead>
<tbody>
@@ -59,11 +59,21 @@
<div @htmx:after-request="fill">
{{ billing_infos_form }}
</div>
{% include "core/base/notifications.jinja" %}
<form
method="post"
action="{{ settings.SITH_EBOUTIC_ET_URL }}"
>
{% endif %}
{% include "core/base/notifications.jinja" %}
<form method="post" id="payment-form">
{# In order to have one CGV button for both payment means,
there is only one form, which action will be given
the `formaction` attribute of the selected submit input #}
<div class="form-group margin-bottom">
<input type="checkbox" id="cgv" name="cgv" required>
<label for="cgv">
{% trans trimmed %}I have read and I accept{% endtrans %}
<a href="{{ url('core:page', 'cgv') }}">{% trans %}the general terms and conditions{% endtrans%}</a>
{%trans%}of the student association of the UTBM{% endtrans %}
</label>
</div>
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
<template x-for="[key, value] in Object.entries(data)" :key="key">
<input type="hidden" :name="key" :value="value">
</template>
@@ -77,24 +87,23 @@
:disabled="!isCbAvailable"
{% endif %}
class="btn btn-blue"
formaction="{{ settings.SITH_EBOUTIC_ET_URL }}"
value="{% trans %}Pay with credit card{% endtrans %}"
/>
</form>
{% else %}
<div class="alert alert-yellow">
{% trans trimmed %}
Credit card payments are currently disabled on the eboutic.
You may still refill your account in one of the AE counters.
Please excuse us for the inconvenience.
{% endtrans %}
</div>
{% endif %}
{% if basket.contains_refilling_item %}
<p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p>
{% elif basket.total > user.account_balance %}
<p>{% trans %}AE account payment disabled because you do not have enough money remaining.{% endtrans %}</p>
{% else %}
<form method="post" action="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}" name="sith-pay-form">
{% else %}
<div class="alert alert-yellow">
{% trans trimmed %}
Credit card payments are currently disabled on the eboutic.
You may still refill your account in one of the AE counters.
Please excuse us for the inconvenience.
{% endtrans %}
</div>
{% endif %}
{% if basket.contains_refilling_item %}
<p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p>
{% elif basket.total > user.account_balance %}
<p>{% trans %}AE account payment disabled because you do not have enough money remaining.{% endtrans %}</p>
{% else %}
{% csrf_token %}
<input
{% if basket.is_expired %}
@@ -104,9 +113,10 @@
{% endif %}
class="btn btn-blue"
type="submit"
formaction="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}"
value="{% trans %}Pay with Sith account{% endtrans %}"
/>
</form>
{% endif %}
{% endif %}
</form>
</div>
{% endblock %}
+15 -2
View File
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-15 11:46+0200\n"
"POT-Creation-Date: 2026-05-30 20:12+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -683,6 +683,7 @@ msgstr "Étiquette"
#: core/templates/core/user_account_detail.jinja
#: core/templates/core/user_stats.jinja
#: counter/templates/counter/last_ops.jinja
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "Quantity"
msgstr "Quantité"
@@ -966,7 +967,7 @@ msgstr "rôle de club membre"
msgid "Benefit"
msgstr "Bénéfice"
#: club/views.py
#: club/views.py eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "Unit price"
msgstr "Prix unitaire"
@@ -4367,6 +4368,18 @@ msgstr "Solde actuel : "
msgid "Remaining account amount: "
msgstr "Solde restant : "
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "I have read and I accept"
msgstr "J'ai lu et j'accepte"
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "the general terms and conditions"
msgstr "les conditions générales de vente"
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "of the student association of the UTBM"
msgstr "de l'Association des étudiants de l'UTBM"
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "Pay with credit card"
msgstr "Payer avec une carte bancaire"
+1 -1
View File
@@ -35,7 +35,7 @@ dependencies = [
"django-ordered-model>=3.7.4,<4.0.0",
"django-simple-captcha>=0.6.3,<1.0.0",
"python-dateutil>=2.9.0.post0,<3.0.0.0",
"sentry-sdk>=2.61.1,<3.0.0",
"sentry-sdk>=2.60.0,<3.0.0",
"jinja2>=3.1.6,<4.0.0",
"django-countries>=8.2.0,<9.0.0",
"dict2xml>=1.7.8,<2.0.0",