diff --git a/core/static/bundled/htmx-index.js b/core/static/bundled/htmx-index.js index 56edea4a..474617ac 100644 --- a/core/static/bundled/htmx-index.js +++ b/core/static/bundled/htmx-index.js @@ -1,3 +1,11 @@ import htmx from "htmx.org"; +document.body.addEventListener("htmx:beforeRequest", (event) => { + event.target.ariaBusy = true; +}); + +document.body.addEventListener("htmx:afterRequest", (event) => { + event.originalTarget.ariaBusy = null; +}); + Object.assign(window, { htmx }); diff --git a/core/static/bundled/utils/web-components.ts b/core/static/bundled/utils/web-components.ts index 8bec98f9..c2b089c5 100644 --- a/core/static/bundled/utils/web-components.ts +++ b/core/static/bundled/utils/web-components.ts @@ -6,7 +6,16 @@ **/ export function registerComponent(name: string, options?: ElementDefinitionOptions) { return (component: CustomElementConstructor) => { - window.customElements.define(name, component, options); + try { + window.customElements.define(name, component, options); + } catch (e) { + if (e instanceof DOMException) { + // biome-ignore lint/suspicious/noConsole: it's handy to troobleshot + console.warn(e.message); + return; + } + throw e; + } }; } diff --git a/core/templates/core/user_preferences.jinja b/core/templates/core/user_preferences.jinja index 0cf4bd57..722e7c44 100644 --- a/core/templates/core/user_preferences.jinja +++ b/core/templates/core/user_preferences.jinja @@ -35,35 +35,12 @@ {% endif %} - {% if profile.customer %} -

{% trans %}Student cards{% endtrans %}

- - {% if profile.customer.student_cards.exists() %} - - {% else %} - {% trans %}No student card registered.{% endtrans %} -

- {% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually - add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %} -

- {% endif %} - -
- {% csrf_token %} - {{ student_card_form.as_p() }} - -
+ {% if student_card %} + {{ student_card }} +

+ {% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually + add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %} +

{% endif %} {% endblock %} \ No newline at end of file diff --git a/core/utils.py b/core/utils.py index 5b6191f6..cdd72fa6 100644 --- a/core/utils.py +++ b/core/utils.py @@ -13,22 +13,41 @@ # # +from dataclasses import dataclass from datetime import date # Image utils from io import BytesIO -from typing import Optional +from typing import Any import PIL from django.conf import settings from django.core.files.base import ContentFile +from django.forms import BaseForm from django.http import HttpRequest +from django.template.loader import render_to_string +from django.utils.html import SafeString from django.utils.timezone import localdate from PIL import ExifTags from PIL.Image import Image, Resampling -def get_start_of_semester(today: Optional[date] = None) -> date: +@dataclass +class FormFragmentTemplateData[T: BaseForm]: + """Dataclass used to pre-render form fragments""" + + form: T + template: str + context: dict[str, Any] + + def render(self, request: HttpRequest) -> SafeString: + # Request is needed for csrf_tokens + return render_to_string( + self.template, context={"form": self.form, **self.context}, request=request + ) + + +def get_start_of_semester(today: date | None = None) -> date: """Return the date of the start of the semester of the given date. If no date is given, return the start date of the current semester. @@ -58,7 +77,7 @@ def get_start_of_semester(today: Optional[date] = None) -> date: return autumn.replace(year=autumn.year - 1) -def get_semester_code(d: Optional[date] = None) -> str: +def get_semester_code(d: date | None = None) -> str: """Return the semester code of the given date. If no date is given, return the semester code of the current semester. diff --git a/core/views/user.py b/core/views/user.py index e9694a92..2c6b01fc 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -70,8 +70,8 @@ from core.views.forms import ( UserGodfathersForm, UserProfileForm, ) -from counter.forms import StudentCardForm from counter.models import Refilling, Selling +from counter.views.student_card import StudentCardFormView from eboutic.models import Invoice from subscription.models import Subscription from trombi.views import UserTrombiForm @@ -576,9 +576,10 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): hasattr(self.object, "trombi_user") and self.request.user.trombi_user.trombi ): kwargs["trombi_form"] = UserTrombiForm() - if hasattr(self.object, "customer"): - kwargs["student_card_form"] = StudentCardForm() + kwargs["student_card"] = StudentCardFormView.get_template_data( + self.object.customer + ).render(self.request) return kwargs diff --git a/counter/forms.py b/counter/forms.py index 84a92512..91e7c3dc 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -45,9 +45,7 @@ class BillingInfoForm(forms.ModelForm): class StudentCardForm(forms.ModelForm): - """Form for adding student cards - Only used for user profile since CounterClick is to complicated. - """ + """Form for adding student cards""" class Meta: model = StudentCard @@ -114,14 +112,6 @@ class GetUserForm(forms.Form): return cleaned_data -class NFCCardForm(forms.Form): - student_card_uid = forms.CharField( - max_length=StudentCard.UID_SIZE, - required=False, - widget=NFCTextInput, - ) - - class RefillForm(forms.ModelForm): error_css_class = "error" required_css_class = "required" diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index cb6bb9cf..7c36b01b 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -29,26 +29,9 @@ {{ user_mini_profile(customer.user) }} {{ user_subscription(customer.user) }}

{% trans %}Amount: {% endtrans %}{{ customer.amount }} €

-
- {% csrf_token %} - - {% trans %}Add a student card{% endtrans %} - {{ student_card_input.student_card_uid }} - {% if request.session['not_valid_student_card_uid'] %} -

{% trans %}This is not a valid student card UID{% endtrans %}

- {% endif %} - -
-
{% trans %}Registered cards{% endtrans %}
- {% if student_cards %} - - {% else %} - {% trans %}No card registered{% endtrans %} + {% if counter.type == 'BAR' %} + {{ student_card }} {% endif %} diff --git a/counter/templates/counter/fragments/create_student_card.jinja b/counter/templates/counter/fragments/create_student_card.jinja new file mode 100644 index 00000000..ab846c55 --- /dev/null +++ b/counter/templates/counter/fragments/create_student_card.jinja @@ -0,0 +1,29 @@ +
+

{% trans %}Add a student card{% endtrans %}

+
+ {% csrf_token %} + {{ form.as_p() }} + + +
+
{% trans %}Registered cards{% endtrans %}
+ {% if student_cards %} + + + {% else %} + {% trans %}No student card registered.{% endtrans %} + {% endif %} +
diff --git a/counter/tests/test_customer.py b/counter/tests/test_customer.py index 2d7e1c60..f7e599e6 100644 --- a/counter/tests/test_customer.py +++ b/counter/tests/test_customer.py @@ -1,15 +1,27 @@ import json import string +from datetime import timedelta import pytest +from django.conf import settings +from django.contrib.auth.base_user import make_password from django.test import Client, TestCase from django.urls import reverse +from django.utils.timezone import now from model_bakery import baker -from core.baker_recipes import subscriber_user +from club.models import Membership +from core.baker_recipes import board_user, subscriber_user from core.models import User from counter.baker_recipes import refill_recipe, sale_recipe -from counter.models import BillingInfo, Counter, Customer, Refilling, Selling +from counter.models import ( + BillingInfo, + Counter, + Customer, + Refilling, + Selling, + StudentCard, +) @pytest.mark.django_db @@ -162,148 +174,269 @@ class TestStudentCard(TestCase): @classmethod def setUpTestData(cls): - cls.krophil = User.objects.get(username="krophil") - cls.sli = User.objects.get(username="sli") - cls.skia = User.objects.get(username="skia") - cls.root = User.objects.get(username="root") + cls.customer = subscriber_user.make() + cls.customer.save() + cls.barmen = subscriber_user.make(password=make_password("plop")) + cls.board_admin = board_user.make() + cls.club_admin = baker.make(User) + cls.root = baker.make(User, is_superuser=True) + cls.subscriber = subscriber_user.make() - cls.counter = Counter.objects.get(id=2) + cls.counter = baker.make(Counter, type="BAR") + cls.counter.sellers.add(cls.barmen) + + cls.club_counter = baker.make(Counter) + baker.make( + Membership, + start_date=now() - timedelta(days=30), + club=cls.club_counter.club, + role=settings.SITH_CLUB_ROLES_ID["Board member"], + user=cls.club_admin, + ) + + cls.valid_card = baker.make( + StudentCard, customer=cls.customer.customer, uid="8A89B82018B0A0" + ) def setUp(self): # Auto login on counter self.client.post( reverse("counter:login", args=[self.counter.id]), - {"username": "krophil", "password": "plop"}, + {"username": self.barmen.username, "password": "plop"}, ) def test_search_user_with_student_card(self): response = self.client.post( reverse("counter:details", args=[self.counter.id]), - {"code": "9A89B82018B0A0"}, + {"code": self.valid_card.uid}, ) assert response.url == reverse( "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + kwargs={"counter_id": self.counter.id, "user_id": self.customer.id}, ) def test_add_student_card_from_counter(self): # Test card with mixed letters and numbers response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": "8B90734A802A8F"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": "8B90734A802A8F", "action": "add_student_card"}, ) - self.assertContains(response, text="8B90734A802A8F") + assert response.status_code == 302 + self.assertContains(self.client.get(response.url), text="8B90734A802A8F") # Test card with only numbers response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": "04786547890123"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": "04786547890123", "action": "add_student_card"}, ) - self.assertContains(response, text="04786547890123") + assert response.status_code == 302 + self.assertContains(self.client.get(response.url), text="04786547890123") # Test card with only letters response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": "ABCAAAFAAFAAAB"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": "ABCAAAFAAFAAAB", "action": "add_student_card"}, ) - self.assertContains(response, text="ABCAAAFAAFAAAB") + assert response.status_code == 302 + self.assertContains(self.client.get(response.url), text="ABCAAAFAAFAAAB") def test_add_student_card_from_counter_fail(self): # UID too short response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": "8B90734A802A8"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": "8B90734A802A8", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" ) + self.assertContains(response, text="Cet UID est invalide") # UID too long response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": "8B90734A802A8FA"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": "8B90734A802A8FA", "action": "add_student_card"}, ) + self.assertContains(response, text="Cet UID est invalide") self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" + response, + text="Assurez-vous que cette valeur comporte au plus 14 caractères (actuellement 15).", ) # Test with already existing card response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": self.valid_card.uid}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": "9A89B82018B0A0", "action": "add_student_card"}, ) + self.assertContains(response, text="Cet UID est invalide") self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" + response, text="Un objet Student card avec ce champ Uid existe déjà." ) # Test with lowercase response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": "8b90734a802a9f"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": "8b90734a802a9f", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" ) + self.assertContains(response, text="Cet UID est invalide") # Test with white spaces response = self.client.post( reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": " "}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, + }, ), - {"student_card_uid": " ", "action": "add_student_card"}, ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" + self.assertContains(response, text="Cet UID est invalide") + self.assertContains(response, text="Ce champ est obligatoire.") + + def test_add_student_card_from_counter_unauthorized(self): + # Send to a counter where you aren't logged in + self.client.post( + reverse("counter:logout", args=[self.counter.id]), + {"user_id": self.barmen.id}, ) + def send_valid_request(client, counter_id): + return client.post( + reverse( + "counter:add_student_card", + kwargs={ + "customer_id": self.customer.customer.pk, + }, + ), + {"uid": "8B90734A802A8F"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={ + "counter_id": counter_id, + "user_id": self.customer.customer.pk, + }, + ), + ) + + assert send_valid_request(self.client, self.counter.id).status_code == 403 + + # Send to a non bar counter + self.client.force_login(self.club_admin) + assert send_valid_request(self.client, self.club_counter.id).status_code == 403 + def test_delete_student_card_with_owner(self): - self.client.force_login(self.sli) + self.client.force_login(self.customer) self.client.post( reverse( "counter:delete_student_card", kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, + "customer_id": self.customer.customer.pk, + "card_id": self.customer.customer.student_cards.first().id, }, ) ) - assert not self.sli.customer.student_cards.exists() + assert not self.customer.customer.student_cards.exists() def test_delete_student_card_with_board_member(self): - self.client.force_login(self.skia) + self.client.force_login(self.board_admin) self.client.post( reverse( "counter:delete_student_card", kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, + "customer_id": self.customer.customer.pk, + "card_id": self.customer.customer.student_cards.first().id, }, ) ) - assert not self.sli.customer.student_cards.exists() + assert not self.customer.customer.student_cards.exists() def test_delete_student_card_with_root(self): self.client.force_login(self.root) @@ -311,100 +444,107 @@ class TestStudentCard(TestCase): reverse( "counter:delete_student_card", kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, + "customer_id": self.customer.customer.pk, + "card_id": self.customer.customer.student_cards.first().id, }, ) ) - assert not self.sli.customer.student_cards.exists() + assert not self.customer.customer.student_cards.exists() def test_delete_student_card_fail(self): - self.client.force_login(self.krophil) + self.client.force_login(self.subscriber) response = self.client.post( reverse( "counter:delete_student_card", kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, + "customer_id": self.customer.customer.pk, + "card_id": self.customer.customer.student_cards.first().id, }, ) ) assert response.status_code == 403 - assert self.sli.customer.student_cards.exists() + assert self.customer.customer.student_cards.exists() def test_add_student_card_from_user_preferences(self): # Test with owner of the card - self.client.force_login(self.sli) - self.client.post( + self.client.force_login(self.customer) + response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "8B90734A802A8F"}, ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) + assert response.status_code == 302 + + response = self.client.get(response.url) self.assertContains(response, text="8B90734A802A8F") # Test with board member - self.client.force_login(self.skia) - self.client.post( + self.client.force_login(self.board_admin) + response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "8B90734A802A8A"}, ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) + assert response.status_code == 302 + + response = self.client.get(response.url) self.assertContains(response, text="8B90734A802A8A") # Test card with only numbers - self.client.post( + response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "04786547890123"}, ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) + assert response.status_code == 302 + + response = self.client.get(response.url) self.assertContains(response, text="04786547890123") # Test card with only letters - self.client.post( + response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "ABCAAAFAAFAAAB"}, ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) + + assert response.status_code == 302 + + response = self.client.get(response.url) self.assertContains(response, text="ABCAAAFAAFAAAB") # Test with root self.client.force_login(self.root) - self.client.post( + response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "8B90734A802A8B"}, ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) + assert response.status_code == 302 + + response = self.client.get(response.url) self.assertContains(response, text="8B90734A802A8B") def test_add_student_card_from_user_preferences_fail(self): - self.client.force_login(self.sli) + self.client.force_login(self.customer) # UID too short response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "8B90734A802A8"}, ) @@ -414,7 +554,8 @@ class TestStudentCard(TestCase): # UID too long response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "8B90734A802A8FA"}, ) @@ -423,9 +564,10 @@ class TestStudentCard(TestCase): # Test with already existing card response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), - {"uid": "9A89B82018B0A0"}, + {"uid": self.valid_card.uid}, ) self.assertContains( response, text="Un objet Student card avec ce champ Uid existe déjà." @@ -434,7 +576,8 @@ class TestStudentCard(TestCase): # Test with lowercase response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "8b90734a802a9f"}, ) @@ -443,17 +586,19 @@ class TestStudentCard(TestCase): # Test with white spaces response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": " " * 14}, ) self.assertContains(response, text="Cet UID est invalide") # Test with unauthorized user - self.client.force_login(self.krophil) + self.client.force_login(self.subscriber) response = self.client.post( reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, ), {"uid": "8B90734A802A8F"}, ) diff --git a/counter/urls.py b/counter/urls.py index d5247478..e196894f 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -52,7 +52,10 @@ from counter.views.home import ( CounterMain, ) from counter.views.invoice import InvoiceCallView -from counter.views.student_card import StudentCardDeleteView, StudentCardFormView +from counter.views.student_card import ( + StudentCardDeleteView, + StudentCardFormView, +) urlpatterns = [ path("/", CounterMain.as_view(), name="details"), diff --git a/counter/utils.py b/counter/utils.py index 2b9b6fd6..499b2d8e 100644 --- a/counter/utils.py +++ b/counter/utils.py @@ -22,14 +22,22 @@ def is_logged_in_counter(request: HttpRequest) -> bool: to the counter) - The current session has a counter token associated with it. - A counter with this token exists. + - The counter is open """ referer_ok = ( "HTTP_REFERER" in request.META and resolve(urlparse(request.META["HTTP_REFERER"]).path).app_name == "counter" ) - return ( + has_token = ( (referer_ok or request.resolver_match.app_name == "counter") and "counter_token" in request.session and request.session["counter_token"] - and Counter.objects.filter(token=request.session["counter_token"]).exists() + ) + if not has_token: + return False + + return ( + Counter.objects.annotate_is_open() + .filter(token=request.session["counter_token"], is_open=True) + .exists() ) diff --git a/counter/views/click.py b/counter/views/click.py index 88875fe4..2fa9684d 100644 --- a/counter/views/click.py +++ b/counter/views/click.py @@ -27,9 +27,10 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic import DetailView from core.views import CanViewMixin -from counter.forms import NFCCardForm, RefillForm -from counter.models import Counter, Customer, Product, Selling, StudentCard +from counter.forms import RefillForm +from counter.models import Counter, Customer, Product, Selling from counter.views.mixins import CounterTabsMixin +from counter.views.student_card import StudentCardFormView if TYPE_CHECKING: from core.models import User @@ -134,7 +135,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session["too_young"] = False request.session["not_allowed"] = False request.session["no_age"] = False - request.session["not_valid_student_card_uid"] = False if self.object.type != "BAR": self.operator = request.user elif self.customer_is_barman(): @@ -146,8 +146,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): action = parse_qs(request.body.decode()).get("action", [""])[0] if action == "add_product": self.add_product(request) - elif action == "add_student_card": - self.add_student_card(request) elif action == "del_product": self.del_product(request) elif action == "refill": @@ -284,23 +282,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session.modified = True return True - def add_student_card(self, request): - """Add a new student card on the customer account.""" - uid = str(request.POST["student_card_uid"]) - if not StudentCard.is_valid(uid): - request.session["not_valid_student_card_uid"] = True - return False - - if not ( - self.object.type == "BAR" - and "counter_token" in request.session - and request.session["counter_token"] == self.object.token - and self.object.is_open - ): - raise PermissionDenied - StudentCard(customer=self.customer, uid=uid).save() - return True - def del_product(self, request): """Delete a product from the basket.""" pid = parse_qs(request.body.decode())["product_id"][0] @@ -431,10 +412,10 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): product ) kwargs["customer"] = self.customer - kwargs["student_cards"] = self.customer.student_cards.all() - kwargs["student_card_input"] = NFCCardForm() kwargs["basket_total"] = self.sum_basket(self.request) kwargs["refill_form"] = self.refill_form or RefillForm() - kwargs["student_card_max_uid_size"] = StudentCard.UID_SIZE kwargs["barmens_can_refill"] = self.object.can_refill() + kwargs["student_card"] = StudentCardFormView.get_template_data( + self.customer + ).render(self.request) return kwargs diff --git a/counter/views/student_card.py b/counter/views/student_card.py index b39cc519..99f67316 100644 --- a/counter/views/student_card.py +++ b/counter/views/student_card.py @@ -13,14 +13,18 @@ # # + from django.core.exceptions import PermissionDenied +from django.http import HttpRequest from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.views.generic.edit import DeleteView, FormView +from core.utils import FormFragmentTemplateData from core.views import CanEditMixin from counter.forms import StudentCardForm from counter.models import Customer, StudentCard +from counter.utils import is_logged_in_counter class StudentCardDeleteView(DeleteView, CanEditMixin): @@ -41,15 +45,38 @@ class StudentCardDeleteView(DeleteView, CanEditMixin): class StudentCardFormView(FormView): - """Add a new student card.""" + """Add a new student card. This is a fragment view !""" form_class = StudentCardForm - template_name = "core/create.jinja" + template_name = "counter/fragments/create_student_card.jinja" - def dispatch(self, request, *args, **kwargs): - self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) - if not StudentCard.can_create(self.customer, request.user): + @classmethod + def get_template_data( + cls, customer: Customer + ) -> FormFragmentTemplateData[form_class]: + """Get necessary data to pre-render the fragment""" + return FormFragmentTemplateData[cls.form_class]( + form=cls.form_class(), + template=cls.template_name, + context={ + "action": reverse_lazy( + "counter:add_student_card", kwargs={"customer_id": customer.pk} + ), + "customer": customer, + "student_cards": customer.student_cards.all(), + }, + ) + + def dispatch(self, request: HttpRequest, *args, **kwargs): + self.customer = get_object_or_404( + Customer.objects.prefetch_related("student_cards"), pk=kwargs["customer_id"] + ) + + if not is_logged_in_counter(request) and not StudentCard.can_create( + self.customer, request.user + ): raise PermissionDenied + return super().dispatch(request, *args, **kwargs) def form_valid(self, form): @@ -58,7 +85,11 @@ class StudentCardFormView(FormView): StudentCard(customer=self.customer, uid=data["uid"]).save() return res + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + data = self.get_template_data(self.customer) + context.update(data.context) + return context + def get_success_url(self, **kwargs): - return reverse_lazy( - "core:user_prefs", kwargs={"user_id": self.customer.user.pk} - ) + return self.request.path diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 0a7ce5fe..9fd39f03 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-29 18:04+0100\n" +"POT-Creation-Date: 2024-12-08 00:29+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -218,7 +218,7 @@ msgstr "Compte" msgid "Company" msgstr "Entreprise" -#: accounting/models.py:307 core/models.py:338 sith/settings.py:421 +#: accounting/models.py:307 core/models.py:338 sith/settings.py:423 msgid "Other" msgstr "Autre" @@ -369,7 +369,7 @@ msgstr "Compte en banque : " #: core/templates/core/user_clubs.jinja:34 #: core/templates/core/user_clubs.jinja:63 #: core/templates/core/user_edit.jinja:62 -#: core/templates/core/user_preferences.jinja:48 +#: counter/templates/counter/fragments/create_student_card.jinja:21 #: counter/templates/counter/last_ops.jinja:35 #: counter/templates/counter/last_ops.jinja:65 #: election/templates/election/election_detail.jinja:191 @@ -517,7 +517,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:467 +#: sith/settings.py:469 msgid "Closed" msgstr "Fermé" @@ -650,8 +650,8 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:955 -#: pedagogy/templates/pedagogy/moderation.jinja:13 +#: counter/templates/counter/cash_summary_list.jinja:37 +#: counter/views/cash.py:87 pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:142 #: trombi/templates/trombi/comment.jinja:4 #: trombi/templates/trombi/comment.jinja:8 @@ -771,7 +771,6 @@ msgstr "Opération liée : " #: core/templates/core/user_godfathers_tree.jinja:85 #: core/templates/core/user_preferences.jinja:18 #: core/templates/core/user_preferences.jinja:27 -#: core/templates/core/user_preferences.jinja:65 #: counter/templates/counter/cash_register_summary.jinja:28 #: forum/templates/forum/reply.jinja:39 #: subscription/templates/subscription/fragments/creation_form.jinja:9 @@ -951,11 +950,11 @@ msgstr "Une action est requise" msgid "You must specify at least an user or an email address" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" -#: club/forms.py:149 counter/forms.py:203 +#: club/forms.py:149 counter/forms.py:193 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:206 +#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:196 #: election/views.py:170 subscription/forms.py:21 msgid "End date" msgstr "Date de fin" @@ -963,15 +962,16 @@ msgstr "Date de fin" #: club/forms.py:156 club/templates/club/club_sellings.jinja:49 #: core/templates/core/user_account_detail.jinja:17 #: core/templates/core/user_account_detail.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:137 +#: counter/templates/counter/cash_summary_list.jinja:33 +#: counter/views/mixins.py:58 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:163 counter/views.py:683 +#: club/forms.py:163 counter/views/mixins.py:94 msgid "Products" msgstr "Produits" -#: club/forms.py:168 counter/views.py:688 +#: club/forms.py:168 counter/views/mixins.py:99 msgid "Archived products" msgstr "Produits archivés" @@ -1334,7 +1334,7 @@ msgid "No mailing list existing for this club" msgstr "Aucune mailing liste n'existe pour ce club" #: club/templates/club/mailing.jinja:72 -#: subscription/templates/subscription/subscription.jinja:39 +#: subscription/templates/subscription/subscription.jinja:38 msgid "New member" msgstr "Nouveau membre" @@ -1426,7 +1426,8 @@ msgstr "Hebdomadaire" msgid "Call" msgstr "Appel" -#: com/models.py:67 com/models.py:174 com/models.py:248 election/models.py:12 +#: com/models.py:67 com/models.py:174 com/models.py:248 +#: core/templates/core/macros.jinja:301 election/models.py:12 #: election/models.py:114 election/models.py:152 forum/models.py:256 #: forum/models.py:310 pedagogy/models.py:97 msgid "title" @@ -1835,6 +1836,7 @@ msgid "Articles in no weekmail yet" msgstr "Articles dans aucun weekmail" #: com/templates/com/weekmail.jinja:20 com/templates/com/weekmail.jinja:49 +#: core/templates/core/macros.jinja:301 msgid "Content" msgstr "Contenu" @@ -2505,7 +2507,7 @@ msgstr "Photos" #: eboutic/templates/eboutic/eboutic_main.jinja:22 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:16 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:420 sith/settings.py:428 +#: sith/settings.py:422 sith/settings.py:430 msgid "Eboutic" msgstr "Eboutic" @@ -2583,7 +2585,7 @@ msgstr "Confirmation" #: core/templates/core/delete_confirm.jinja:20 #: core/templates/core/file_delete_confirm.jinja:46 -#: counter/templates/counter/counter_click.jinja:121 +#: counter/templates/counter/counter_click.jinja:111 #: sas/templates/sas/ask_picture_removal.jinja:20 msgid "Cancel" msgstr "Annuler" @@ -3042,11 +3044,11 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:54 -#: core/templates/core/user_tools.jinja:58 counter/views.py:708 +#: core/templates/core/user_tools.jinja:58 counter/views/mixins.py:119 msgid "Etickets" msgstr "Etickets" -#: core/templates/core/user_account.jinja:69 core/views/user.py:638 +#: core/templates/core/user_account.jinja:69 core/views/user.py:639 msgid "User has no account" msgstr "L'utilisateur n'a pas de compte" @@ -3137,7 +3139,7 @@ msgstr "Non cotisant" #: core/templates/core/user_detail.jinja:162 #: subscription/templates/subscription/subscription.jinja:6 -#: subscription/templates/subscription/subscription.jinja:37 +#: subscription/templates/subscription/subscription.jinja:36 msgid "New subscription" msgstr "Nouvelle cotisation" @@ -3295,19 +3297,11 @@ msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s." msgid "Go to my Trombi tools" msgstr "Allez à mes outils de Trombi" -#: core/templates/core/user_preferences.jinja:39 -msgid "Student cards" -msgstr "Cartes étudiante" - -#: core/templates/core/user_preferences.jinja:54 -msgid "No student card registered." -msgstr "Aucune carte étudiante enregistrée." - -#: core/templates/core/user_preferences.jinja:56 +#: core/templates/core/user_preferences.jinja:49 msgid "" "You can add a card by asking at a counter or add it yourself here. If you " "want to manually\n" -" add a student card yourself, you'll need a NFC reader. We store " +" add a student card yourself, you'll need a NFC reader. We store " "the UID of the card which is 14 characters long." msgstr "" "Vous pouvez ajouter une carte en demandant à un comptoir ou en l'ajoutant " @@ -3377,8 +3371,8 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:48 counter/forms.py:176 -#: counter/views.py:678 +#: core/templates/core/user_tools.jinja:48 counter/forms.py:166 +#: counter/views/mixins.py:89 msgid "Counters" msgstr "Comptoirs" @@ -3395,12 +3389,13 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:698 +#: counter/templates/counter/cash_summary_list.jinja:23 +#: counter/views/mixins.py:109 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:57 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:703 +#: counter/templates/counter/invoices_call.jinja:4 counter/views/mixins.py:114 msgid "Invoices call" msgstr "Appels à facture" @@ -3548,7 +3543,7 @@ msgstr "Parrain / Marraine" msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:310 counter/forms.py:82 trombi/views.py:151 +#: core/views/forms.py:310 counter/forms.py:80 trombi/views.py:151 msgid "Select user" msgstr "Choisir un utilisateur" @@ -3601,15 +3596,15 @@ msgstr "Galaxie" msgid "counter" msgstr "comptoir" -#: counter/forms.py:63 +#: counter/forms.py:61 msgid "This UID is invalid" msgstr "Cet UID est invalide" -#: counter/forms.py:111 +#: counter/forms.py:109 msgid "User not found" msgstr "Utilisateur non trouvé" -#: counter/management/commands/dump_accounts.py:141 +#: counter/management/commands/dump_accounts.py:148 msgid "Your AE account has been emptied" msgstr "Votre compte AE a été vidé" @@ -3637,7 +3632,7 @@ msgstr "client" msgid "customers" msgstr "clients" -#: counter/models.py:110 counter/views.py:261 +#: counter/models.py:110 counter/views/click.py:66 msgid "Not enough money" msgstr "Solde insuffisant" @@ -3777,8 +3772,8 @@ msgstr "quantité" msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:797 sith/settings.py:413 sith/settings.py:418 -#: sith/settings.py:438 +#: counter/models.py:797 sith/settings.py:415 sith/settings.py:420 +#: sith/settings.py:440 msgid "Credit card" msgstr "Carte bancaire" @@ -3910,7 +3905,8 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:956 +#: counter/templates/counter/cash_summary_list.jinja:36 +#: counter/views/cash.py:88 msgid "Emptied" msgstr "Coffre vidé" @@ -3922,17 +3918,14 @@ msgstr "oui" msgid "There is no cash register summary in this website." msgstr "Il n'y a pas de relevé de caisse dans ce site web." -#: counter/templates/counter/counter_click.jinja:35 -msgid "Add a student card" -msgstr "Ajouter une carte étudiante" +#: counter/templates/counter/counter_click.jinja:46 +#: launderette/templates/launderette/launderette_admin.jinja:8 +msgid "Selling" +msgstr "Vente" -#: counter/templates/counter/counter_click.jinja:38 -msgid "This is not a valid student card UID" -msgstr "Ce n'est pas un UID de carte étudiante valide" - -#: counter/templates/counter/counter_click.jinja:40 -#: counter/templates/counter/counter_click.jinja:67 -#: counter/templates/counter/counter_click.jinja:132 +#: counter/templates/counter/counter_click.jinja:57 +#: counter/templates/counter/counter_click.jinja:122 +#: counter/templates/counter/fragments/create_student_card.jinja:10 #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 @@ -3941,29 +3934,16 @@ msgstr "Ce n'est pas un UID de carte étudiante valide" msgid "Go" msgstr "Valider" -#: counter/templates/counter/counter_click.jinja:42 -msgid "Registered cards" -msgstr "Cartes enregistrées" - -#: counter/templates/counter/counter_click.jinja:51 -msgid "No card registered" -msgstr "Aucune carte enregistrée" - -#: counter/templates/counter/counter_click.jinja:56 -#: launderette/templates/launderette/launderette_admin.jinja:8 -msgid "Selling" -msgstr "Vente" - -#: counter/templates/counter/counter_click.jinja:74 +#: counter/templates/counter/counter_click.jinja:64 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:19 msgid "Basket: " msgstr "Panier : " -#: counter/templates/counter/counter_click.jinja:115 +#: counter/templates/counter/counter_click.jinja:105 msgid "Finish" msgstr "Terminer" -#: counter/templates/counter/counter_click.jinja:125 +#: counter/templates/counter/counter_click.jinja:115 #: counter/templates/counter/refilling_list.jinja:9 msgid "Refilling" msgstr "Rechargement" @@ -4047,6 +4027,18 @@ msgstr "Nouveau eticket" msgid "There is no eticket in this website." msgstr "Il n'y a pas de eticket sur ce site web." +#: counter/templates/counter/fragments/create_student_card.jinja:2 +msgid "Add a student card" +msgstr "Ajouter une carte étudiante" + +#: counter/templates/counter/fragments/create_student_card.jinja:13 +msgid "Registered cards" +msgstr "Cartes enregistrées" + +#: counter/templates/counter/fragments/create_student_card.jinja:27 +msgid "No student card registered." +msgstr "Aucune carte étudiante enregistrée." + #: counter/templates/counter/invoices_call.jinja:8 #, python-format msgid "Invoices call for %(date)s" @@ -4219,104 +4211,104 @@ msgstr "Temps" msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:147 -msgid "Cash summary" -msgstr "Relevé de caisse" - -#: counter/views.py:156 -msgid "Last operations" -msgstr "Dernières opérations" - -#: counter/views.py:203 -msgid "Bad credentials" -msgstr "Mauvais identifiants" - -#: counter/views.py:205 -msgid "User is not barman" -msgstr "L'utilisateur n'est pas barman." - -#: counter/views.py:210 -msgid "Bad location, someone is already logged in somewhere else" -msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" - -#: counter/views.py:252 -msgid "Too young for that product" -msgstr "Trop jeune pour ce produit" - -#: counter/views.py:255 -msgid "Not allowed for that product" -msgstr "Non autorisé pour ce produit" - -#: counter/views.py:258 -msgid "No date of birth provided" -msgstr "Pas de date de naissance renseignée" - -#: counter/views.py:546 -msgid "You have not enough money to buy all the basket" -msgstr "Vous n'avez pas assez d'argent pour acheter le panier" - -#: counter/views.py:673 -msgid "Counter administration" -msgstr "Administration des comptoirs" - -#: counter/views.py:693 -msgid "Product types" -msgstr "Types de produit" - -#: counter/views.py:913 +#: counter/views/cash.py:45 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:914 +#: counter/views/cash.py:46 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:915 +#: counter/views/cash.py:47 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:916 +#: counter/views/cash.py:48 msgid "1 euro" msgstr "1 €" -#: counter/views.py:917 +#: counter/views/cash.py:49 msgid "2 euros" msgstr "2 €" -#: counter/views.py:918 +#: counter/views/cash.py:50 msgid "5 euros" msgstr "5 €" -#: counter/views.py:919 +#: counter/views/cash.py:51 msgid "10 euros" msgstr "10 €" -#: counter/views.py:920 +#: counter/views/cash.py:52 msgid "20 euros" msgstr "20 €" -#: counter/views.py:921 +#: counter/views/cash.py:53 msgid "50 euros" msgstr "50 €" -#: counter/views.py:923 +#: counter/views/cash.py:55 msgid "100 euros" msgstr "100 €" -#: counter/views.py:926 counter/views.py:932 counter/views.py:938 -#: counter/views.py:944 counter/views.py:950 +#: counter/views/cash.py:58 counter/views/cash.py:64 counter/views/cash.py:70 +#: counter/views/cash.py:76 counter/views/cash.py:82 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:929 counter/views.py:935 counter/views.py:941 -#: counter/views.py:947 counter/views.py:953 +#: counter/views/cash.py:61 counter/views/cash.py:67 counter/views/cash.py:73 +#: counter/views/cash.py:79 counter/views/cash.py:85 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1473 +#: counter/views/click.py:57 +msgid "Too young for that product" +msgstr "Trop jeune pour ce produit" + +#: counter/views/click.py:60 +msgid "Not allowed for that product" +msgstr "Non autorisé pour ce produit" + +#: counter/views/click.py:63 +msgid "No date of birth provided" +msgstr "Pas de date de naissance renseignée" + +#: counter/views/click.py:331 +msgid "You have not enough money to buy all the basket" +msgstr "Vous n'avez pas assez d'argent pour acheter le panier" + +#: counter/views/eticket.py:120 msgid "people(s)" msgstr "personne(s)" +#: counter/views/home.py:74 +msgid "Bad credentials" +msgstr "Mauvais identifiants" + +#: counter/views/home.py:76 +msgid "User is not barman" +msgstr "L'utilisateur n'est pas barman." + +#: counter/views/home.py:81 +msgid "Bad location, someone is already logged in somewhere else" +msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" + +#: counter/views/mixins.py:68 +msgid "Cash summary" +msgstr "Relevé de caisse" + +#: counter/views/mixins.py:77 +msgid "Last operations" +msgstr "Dernières opérations" + +#: counter/views/mixins.py:84 +msgid "Counter administration" +msgstr "Administration des comptoirs" + +#: counter/views/mixins.py:104 +msgid "Product types" +msgstr "Types de produit" + #: eboutic/forms.py:88 msgid "The request was badly formatted." msgstr "La requête a été mal formatée." @@ -4894,12 +4886,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:656 +#: sith/settings.py:658 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:656 +#: sith/settings.py:658 msgid "Drying" msgstr "Séchage" @@ -5414,380 +5406,380 @@ msgstr "Personne(s)" msgid "Identify users on pictures" msgstr "Identifiez les utilisateurs sur les photos" -#: sith/settings.py:254 sith/settings.py:475 +#: sith/settings.py:253 sith/settings.py:477 msgid "English" msgstr "Anglais" -#: sith/settings.py:254 sith/settings.py:474 +#: sith/settings.py:253 sith/settings.py:476 msgid "French" msgstr "Français" -#: sith/settings.py:394 +#: sith/settings.py:396 msgid "TC" msgstr "TC" -#: sith/settings.py:395 +#: sith/settings.py:397 msgid "IMSI" msgstr "IMSI" -#: sith/settings.py:396 +#: sith/settings.py:398 msgid "IMAP" msgstr "IMAP" -#: sith/settings.py:397 +#: sith/settings.py:399 msgid "INFO" msgstr "INFO" -#: sith/settings.py:398 +#: sith/settings.py:400 msgid "GI" msgstr "GI" -#: sith/settings.py:399 sith/settings.py:485 +#: sith/settings.py:401 sith/settings.py:487 msgid "E" msgstr "E" -#: sith/settings.py:400 +#: sith/settings.py:402 msgid "EE" msgstr "EE" -#: sith/settings.py:401 +#: sith/settings.py:403 msgid "GESC" msgstr "GESC" -#: sith/settings.py:402 +#: sith/settings.py:404 msgid "GMC" msgstr "GMC" -#: sith/settings.py:403 +#: sith/settings.py:405 msgid "MC" msgstr "MC" -#: sith/settings.py:404 +#: sith/settings.py:406 msgid "EDIM" msgstr "EDIM" -#: sith/settings.py:405 +#: sith/settings.py:407 msgid "Humanities" msgstr "Humanités" -#: sith/settings.py:406 +#: sith/settings.py:408 msgid "N/A" msgstr "N/A" -#: sith/settings.py:410 sith/settings.py:417 sith/settings.py:436 +#: sith/settings.py:412 sith/settings.py:419 sith/settings.py:438 msgid "Check" msgstr "Chèque" -#: sith/settings.py:411 sith/settings.py:419 sith/settings.py:437 +#: sith/settings.py:413 sith/settings.py:421 sith/settings.py:439 msgid "Cash" msgstr "Espèces" -#: sith/settings.py:412 +#: sith/settings.py:414 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:425 +#: sith/settings.py:427 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:426 +#: sith/settings.py:428 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:427 +#: sith/settings.py:429 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:455 +#: sith/settings.py:457 msgid "Free" msgstr "Libre" -#: sith/settings.py:456 +#: sith/settings.py:458 msgid "CS" msgstr "CS" -#: sith/settings.py:457 +#: sith/settings.py:459 msgid "TM" msgstr "TM" -#: sith/settings.py:458 +#: sith/settings.py:460 msgid "OM" msgstr "OM" -#: sith/settings.py:459 +#: sith/settings.py:461 msgid "QC" msgstr "QC" -#: sith/settings.py:460 +#: sith/settings.py:462 msgid "EC" msgstr "EC" -#: sith/settings.py:461 +#: sith/settings.py:463 msgid "RN" msgstr "RN" -#: sith/settings.py:462 +#: sith/settings.py:464 msgid "ST" msgstr "ST" -#: sith/settings.py:463 +#: sith/settings.py:465 msgid "EXT" msgstr "EXT" -#: sith/settings.py:468 +#: sith/settings.py:470 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:469 +#: sith/settings.py:471 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:470 +#: sith/settings.py:472 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:476 +#: sith/settings.py:478 msgid "German" msgstr "Allemand" -#: sith/settings.py:477 +#: sith/settings.py:479 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:481 +#: sith/settings.py:483 msgid "A" msgstr "A" -#: sith/settings.py:482 +#: sith/settings.py:484 msgid "B" msgstr "B" -#: sith/settings.py:483 +#: sith/settings.py:485 msgid "C" msgstr "C" -#: sith/settings.py:484 +#: sith/settings.py:486 msgid "D" msgstr "D" -#: sith/settings.py:486 +#: sith/settings.py:488 msgid "FX" msgstr "FX" -#: sith/settings.py:487 +#: sith/settings.py:489 msgid "F" msgstr "F" -#: sith/settings.py:488 +#: sith/settings.py:490 msgid "Abs" msgstr "Abs" -#: sith/settings.py:492 +#: sith/settings.py:494 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:493 +#: sith/settings.py:495 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:537 +#: sith/settings.py:539 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:538 +#: sith/settings.py:540 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:540 +#: sith/settings.py:542 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:544 +#: sith/settings.py:546 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:545 +#: sith/settings.py:547 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:546 +#: sith/settings.py:548 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:547 +#: sith/settings.py:549 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:548 +#: sith/settings.py:550 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:549 +#: sith/settings.py:551 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:550 +#: sith/settings.py:552 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:551 +#: sith/settings.py:553 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:553 +#: sith/settings.py:555 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:557 +#: sith/settings.py:559 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:558 +#: sith/settings.py:560 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:559 +#: sith/settings.py:561 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:561 +#: sith/settings.py:563 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:565 +#: sith/settings.py:567 msgid "One day" msgstr "Un jour" -#: sith/settings.py:566 +#: sith/settings.py:568 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:569 +#: sith/settings.py:571 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:574 +#: sith/settings.py:576 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:579 +#: sith/settings.py:581 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:584 +#: sith/settings.py:586 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:589 +#: sith/settings.py:591 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:595 +#: sith/settings.py:597 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:615 +#: sith/settings.py:617 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:616 +#: sith/settings.py:618 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:617 +#: sith/settings.py:619 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:618 +#: sith/settings.py:620 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:619 +#: sith/settings.py:621 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:620 +#: sith/settings.py:622 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:621 +#: sith/settings.py:623 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:622 +#: sith/settings.py:624 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:623 +#: sith/settings.py:625 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:660 +#: sith/settings.py:662 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:661 +#: sith/settings.py:663 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:664 +#: sith/settings.py:666 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:666 +#: sith/settings.py:668 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:667 +#: sith/settings.py:669 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:668 +#: sith/settings.py:670 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:669 +#: sith/settings.py:671 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:670 +#: sith/settings.py:672 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:671 +#: sith/settings.py:673 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:672 +#: sith/settings.py:674 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:684 +#: sith/settings.py:686 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:685 +#: sith/settings.py:687 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:686 +#: sith/settings.py:688 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:687 +#: sith/settings.py:689 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:688 +#: sith/settings.py:690 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:696 +#: sith/settings.py:698 msgid "AE tee-shirt" msgstr "Tee-shirt AE" @@ -5828,27 +5820,14 @@ msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" msgid "Subscription created for %(user)s" msgstr "Cotisation créée pour %(user)s" -#: subscription/templates/subscription/fragments/creation_success.jinja:8 -#, python-format -msgid "" -"%(user)s received its new %(type)s subscription. It will be active until " -"%(end)s included." -msgstr "" -"%(user)s a reçu sa nouvelle cotisaton %(type)s. Elle sert active jusqu'au " -"%(end)s inclu." - -#: subscription/templates/subscription/fragments/creation_success.jinja:16 +#: subscription/templates/subscription/fragments/creation_success.jinja:19 msgid "Go to user profile" msgstr "Voir le profil de l'utilisateur" -#: subscription/templates/subscription/fragments/creation_success.jinja:24 +#: subscription/templates/subscription/fragments/creation_success.jinja:27 msgid "Create another subscription" msgstr "Créer une nouvelle cotisation" -#: subscription/templates/subscription/subscription.jinja -msgid "Existing member" -msgstr "Membre existant" - #: subscription/templates/subscription/stats.jinja:27 msgid "Total subscriptions" msgstr "Cotisations totales" @@ -5857,6 +5836,10 @@ msgstr "Cotisations totales" msgid "Subscriptions by type" msgstr "Cotisations par type" +#: subscription/templates/subscription/subscription.jinja:38 +msgid "Existing member" +msgstr "Membre existant" + #: trombi/models.py:55 msgid "subscription deadline" msgstr "fin des inscriptions" diff --git a/sas/tests/test_api.py b/sas/tests/test_api.py index ebc33638..733838d2 100644 --- a/sas/tests/test_api.py +++ b/sas/tests/test_api.py @@ -1,10 +1,10 @@ from django.conf import settings +from django.core.cache import cache from django.db import transaction from django.test import TestCase from django.urls import reverse from model_bakery import baker from model_bakery.recipe import Recipe -from pytest_django.asserts import assertNumQueries from core.baker_recipes import old_subscriber_user, subscriber_user from core.models import RealGroup, SithFile, User @@ -128,9 +128,11 @@ class TestPictureSearch(TestSas): def test_num_queries(self): """Test that the number of queries is stable.""" self.client.force_login(subscriber_user.make()) - with assertNumQueries(5): + cache.clear() + with self.assertNumQueries(7): + # 2 requests to create the session # 1 request to fetch the user from the db - # 2 requests to check the user permissions + # 2 requests to check the user permissions, depends on the db engine # 1 request to fetch the pictures # 1 request to count the total number of items in the pagination self.client.get(self.url)