Merge pull request #921 from ae-utbm/counter-click

Casser counter click étape 1 : introduire des fragments
This commit is contained in:
Bartuccio Antoine 2024-12-08 13:49:08 +01:00 committed by GitHub
commit 902cafc5e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 608 additions and 439 deletions

View File

@ -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 });

View File

@ -6,7 +6,16 @@
**/
export function registerComponent(name: string, options?: ElementDefinitionOptions) {
return (component: CustomElementConstructor) => {
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;
}
};
}

View File

@ -35,35 +35,12 @@
{% endif %}
{% if profile.customer %}
<h3>{% trans %}Student cards{% endtrans %}</h3>
{% if profile.customer.student_cards.exists() %}
<ul class="student-cards">
{% for card in profile.customer.student_cards.all() %}
<li>
{{ card.uid }}
&nbsp;-&nbsp;
<a href="{{ url('counter:delete_student_card', customer_id=profile.customer.pk, card_id=card.id) }}">
{% trans %}Delete{% endtrans %}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em>
{% if student_card %}
{{ student_card }}
<p class="justify">
{% 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 %}
</p>
{% endif %}
<form class="form form-cards" action="{{ url('counter:add_student_card', customer_id=profile.customer.pk) }}"
method="post">
{% csrf_token %}
{{ student_card_form.as_p() }}
<input class="form-submit-btn" type="submit" value="{% trans %}Save{% endtrans %}" />
</form>
{% endif %}
</div>
{% endblock %}

View File

@ -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.

View File

@ -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

View File

@ -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"

View File

@ -29,26 +29,9 @@
{{ user_mini_profile(customer.user) }}
{{ user_subscription(customer.user) }}
<p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p>
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
<input type="hidden" name="action" value="add_student_card">
{% trans %}Add a student card{% endtrans %}
{{ student_card_input.student_card_uid }}
{% if request.session['not_valid_student_card_uid'] %}
<p><strong>{% trans %}This is not a valid student card UID{% endtrans %}</strong></p>
{% endif %}
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>
<h6>{% trans %}Registered cards{% endtrans %}</h6>
{% if student_cards %}
<ul>
{% for card in student_cards %}
<li>{{ card.uid }}</li>
{% endfor %}
</ul>
{% else %}
{% trans %}No card registered{% endtrans %}
{% if counter.type == 'BAR' %}
{{ student_card }}
{% endif %}
</div>

View File

@ -0,0 +1,29 @@
<div id="student_card_form">
<h3>{% trans %}Add a student card{% endtrans %}</h3>
<form
hx-post="{{ action }}"
hx-swap="outerHTML"
hx-target="#student_card_form"
>
{% csrf_token %}
{{ form.as_p() }}
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>
<h6>{% trans %}Registered cards{% endtrans %}</h6>
{% if student_cards %}
<ul>
{% for card in student_cards %}
<li>
{{ card.uid }}
<a href="{{ url('counter:delete_student_card', customer_id=customer.pk, card_id=card.id) }}">
{% trans %}Delete{% endtrans %}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em>
{% endif %}
</div>

View File

@ -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"},
)

View File

@ -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("<int:counter_id>/", CounterMain.as_view(), name="details"),

View File

@ -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()
)

View File

@ -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

View File

@ -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

View File

@ -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 <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\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,15 +3297,7 @@ 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"
@ -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"

View File

@ -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)