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 %}
-
-
+ {% 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 }} €
-
- {% trans %}Registered cards{% endtrans %}
- {% if student_cards %}
-
- {% for card in student_cards %}
- - {{ card.uid }}
- {% endfor %}
-
- {% 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 @@
+
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)