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"; 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 }); Object.assign(window, { htmx });

View File

@ -6,7 +6,16 @@
**/ **/
export function registerComponent(name: string, options?: ElementDefinitionOptions) { export function registerComponent(name: string, options?: ElementDefinitionOptions) {
return (component: CustomElementConstructor) => { return (component: CustomElementConstructor) => {
try {
window.customElements.define(name, component, options); 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 %} {% endif %}
{% if profile.customer %} {% if student_card %}
<h3>{% trans %}Student cards{% endtrans %}</h3> {{ student_card }}
{% 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>
<p class="justify"> <p class="justify">
{% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually {% 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 %} 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> </p>
{% endif %} {% 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> </div>
{% endblock %} {% endblock %}

View File

@ -13,22 +13,41 @@
# #
# #
from dataclasses import dataclass
from datetime import date from datetime import date
# Image utils # Image utils
from io import BytesIO from io import BytesIO
from typing import Optional from typing import Any
import PIL import PIL
from django.conf import settings from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.forms import BaseForm
from django.http import HttpRequest 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 django.utils.timezone import localdate
from PIL import ExifTags from PIL import ExifTags
from PIL.Image import Image, Resampling 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. """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. 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) 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. """Return the semester code of the given date.
If no date is given, return the semester code of the current semester. 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, UserGodfathersForm,
UserProfileForm, UserProfileForm,
) )
from counter.forms import StudentCardForm
from counter.models import Refilling, Selling from counter.models import Refilling, Selling
from counter.views.student_card import StudentCardFormView
from eboutic.models import Invoice from eboutic.models import Invoice
from subscription.models import Subscription from subscription.models import Subscription
from trombi.views import UserTrombiForm 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 hasattr(self.object, "trombi_user") and self.request.user.trombi_user.trombi
): ):
kwargs["trombi_form"] = UserTrombiForm() kwargs["trombi_form"] = UserTrombiForm()
if hasattr(self.object, "customer"): 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 return kwargs

View File

@ -45,9 +45,7 @@ class BillingInfoForm(forms.ModelForm):
class StudentCardForm(forms.ModelForm): class StudentCardForm(forms.ModelForm):
"""Form for adding student cards """Form for adding student cards"""
Only used for user profile since CounterClick is to complicated.
"""
class Meta: class Meta:
model = StudentCard model = StudentCard
@ -114,14 +112,6 @@ class GetUserForm(forms.Form):
return cleaned_data 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): class RefillForm(forms.ModelForm):
error_css_class = "error" error_css_class = "error"
required_css_class = "required" required_css_class = "required"

View File

@ -29,26 +29,9 @@
{{ user_mini_profile(customer.user) }} {{ user_mini_profile(customer.user) }}
{{ user_subscription(customer.user) }} {{ user_subscription(customer.user) }}
<p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p> <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> {% if counter.type == 'BAR' %}
{% for card in student_cards %} {{ student_card }}
<li>{{ card.uid }}</li>
{% endfor %}
</ul>
{% else %}
{% trans %}No card registered{% endtrans %}
{% endif %} {% endif %}
</div> </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 json
import string import string
from datetime import timedelta
import pytest import pytest
from django.conf import settings
from django.contrib.auth.base_user import make_password
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now
from model_bakery import baker 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 core.models import User
from counter.baker_recipes import refill_recipe, sale_recipe 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 @pytest.mark.django_db
@ -162,148 +174,269 @@ class TestStudentCard(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.krophil = User.objects.get(username="krophil") cls.customer = subscriber_user.make()
cls.sli = User.objects.get(username="sli") cls.customer.save()
cls.skia = User.objects.get(username="skia") cls.barmen = subscriber_user.make(password=make_password("plop"))
cls.root = User.objects.get(username="root") 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): def setUp(self):
# Auto login on counter # Auto login on counter
self.client.post( self.client.post(
reverse("counter:login", args=[self.counter.id]), 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): def test_search_user_with_student_card(self):
response = self.client.post( response = self.client.post(
reverse("counter:details", args=[self.counter.id]), reverse("counter:details", args=[self.counter.id]),
{"code": "9A89B82018B0A0"}, {"code": self.valid_card.uid},
) )
assert response.url == reverse( assert response.url == reverse(
"counter:click", "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): def test_add_student_card_from_counter(self):
# Test card with mixed letters and numbers # Test card with mixed letters and numbers
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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 # Test card with only numbers
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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 # Test card with only letters
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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): def test_add_student_card_from_counter_fail(self):
# UID too short # UID too short
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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 # UID too long
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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( 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 # Test with already existing card
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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( 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 # Test with lowercase
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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 # Test with white spaces
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:click", "counter:add_student_card",
kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, 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( self.assertContains(response, text="Cet UID est invalide")
response, text="Ce n'est pas un UID de carte étudiante valide" 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): def test_delete_student_card_with_owner(self):
self.client.force_login(self.sli) self.client.force_login(self.customer)
self.client.post( self.client.post(
reverse( reverse(
"counter:delete_student_card", "counter:delete_student_card",
kwargs={ kwargs={
"customer_id": self.sli.customer.pk, "customer_id": self.customer.customer.pk,
"card_id": self.sli.customer.student_cards.first().id, "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): 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( self.client.post(
reverse( reverse(
"counter:delete_student_card", "counter:delete_student_card",
kwargs={ kwargs={
"customer_id": self.sli.customer.pk, "customer_id": self.customer.customer.pk,
"card_id": self.sli.customer.student_cards.first().id, "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): def test_delete_student_card_with_root(self):
self.client.force_login(self.root) self.client.force_login(self.root)
@ -311,100 +444,107 @@ class TestStudentCard(TestCase):
reverse( reverse(
"counter:delete_student_card", "counter:delete_student_card",
kwargs={ kwargs={
"customer_id": self.sli.customer.pk, "customer_id": self.customer.customer.pk,
"card_id": self.sli.customer.student_cards.first().id, "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): def test_delete_student_card_fail(self):
self.client.force_login(self.krophil) self.client.force_login(self.subscriber)
response = self.client.post( response = self.client.post(
reverse( reverse(
"counter:delete_student_card", "counter:delete_student_card",
kwargs={ kwargs={
"customer_id": self.sli.customer.pk, "customer_id": self.customer.customer.pk,
"card_id": self.sli.customer.student_cards.first().id, "card_id": self.customer.customer.student_cards.first().id,
}, },
) )
) )
assert response.status_code == 403 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): def test_add_student_card_from_user_preferences(self):
# Test with owner of the card # Test with owner of the card
self.client.force_login(self.sli) self.client.force_login(self.customer)
self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "8B90734A802A8F"},
) )
response = self.client.get( assert response.status_code == 302
reverse("core:user_prefs", kwargs={"user_id": self.sli.id})
) response = self.client.get(response.url)
self.assertContains(response, text="8B90734A802A8F") self.assertContains(response, text="8B90734A802A8F")
# Test with board member # Test with board member
self.client.force_login(self.skia) self.client.force_login(self.board_admin)
self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "8B90734A802A8A"},
) )
response = self.client.get( assert response.status_code == 302
reverse("core:user_prefs", kwargs={"user_id": self.sli.id})
) response = self.client.get(response.url)
self.assertContains(response, text="8B90734A802A8A") self.assertContains(response, text="8B90734A802A8A")
# Test card with only numbers # Test card with only numbers
self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "04786547890123"},
) )
response = self.client.get( assert response.status_code == 302
reverse("core:user_prefs", kwargs={"user_id": self.sli.id})
) response = self.client.get(response.url)
self.assertContains(response, text="04786547890123") self.assertContains(response, text="04786547890123")
# Test card with only letters # Test card with only letters
self.client.post( response = self.client.post(
reverse( 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"}, {"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") self.assertContains(response, text="ABCAAAFAAFAAAB")
# Test with root # Test with root
self.client.force_login(self.root) self.client.force_login(self.root)
self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "8B90734A802A8B"},
) )
response = self.client.get( assert response.status_code == 302
reverse("core:user_prefs", kwargs={"user_id": self.sli.id})
) response = self.client.get(response.url)
self.assertContains(response, text="8B90734A802A8B") self.assertContains(response, text="8B90734A802A8B")
def test_add_student_card_from_user_preferences_fail(self): 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 # UID too short
response = self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "8B90734A802A8"},
) )
@ -414,7 +554,8 @@ class TestStudentCard(TestCase):
# UID too long # UID too long
response = self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "8B90734A802A8FA"},
) )
@ -423,9 +564,10 @@ class TestStudentCard(TestCase):
# Test with already existing card # Test with already existing card
response = self.client.post( response = self.client.post(
reverse( 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( self.assertContains(
response, text="Un objet Student card avec ce champ Uid existe déjà." response, text="Un objet Student card avec ce champ Uid existe déjà."
@ -434,7 +576,8 @@ class TestStudentCard(TestCase):
# Test with lowercase # Test with lowercase
response = self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "8b90734a802a9f"},
) )
@ -443,17 +586,19 @@ class TestStudentCard(TestCase):
# Test with white spaces # Test with white spaces
response = self.client.post( response = self.client.post(
reverse( 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}, {"uid": " " * 14},
) )
self.assertContains(response, text="Cet UID est invalide") self.assertContains(response, text="Cet UID est invalide")
# Test with unauthorized user # Test with unauthorized user
self.client.force_login(self.krophil) self.client.force_login(self.subscriber)
response = self.client.post( response = self.client.post(
reverse( 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"}, {"uid": "8B90734A802A8F"},
) )

View File

@ -52,7 +52,10 @@ from counter.views.home import (
CounterMain, CounterMain,
) )
from counter.views.invoice import InvoiceCallView from counter.views.invoice import InvoiceCallView
from counter.views.student_card import StudentCardDeleteView, StudentCardFormView from counter.views.student_card import (
StudentCardDeleteView,
StudentCardFormView,
)
urlpatterns = [ urlpatterns = [
path("<int:counter_id>/", CounterMain.as_view(), name="details"), 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) to the counter)
- The current session has a counter token associated with it. - The current session has a counter token associated with it.
- A counter with this token exists. - A counter with this token exists.
- The counter is open
""" """
referer_ok = ( referer_ok = (
"HTTP_REFERER" in request.META "HTTP_REFERER" in request.META
and resolve(urlparse(request.META["HTTP_REFERER"]).path).app_name == "counter" and resolve(urlparse(request.META["HTTP_REFERER"]).path).app_name == "counter"
) )
return ( has_token = (
(referer_ok or request.resolver_match.app_name == "counter") (referer_ok or request.resolver_match.app_name == "counter")
and "counter_token" in request.session and "counter_token" in request.session
and request.session["counter_token"] 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 django.views.generic import DetailView
from core.views import CanViewMixin from core.views import CanViewMixin
from counter.forms import NFCCardForm, RefillForm from counter.forms import RefillForm
from counter.models import Counter, Customer, Product, Selling, StudentCard from counter.models import Counter, Customer, Product, Selling
from counter.views.mixins import CounterTabsMixin from counter.views.mixins import CounterTabsMixin
from counter.views.student_card import StudentCardFormView
if TYPE_CHECKING: if TYPE_CHECKING:
from core.models import User from core.models import User
@ -134,7 +135,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
request.session["too_young"] = False request.session["too_young"] = False
request.session["not_allowed"] = False request.session["not_allowed"] = False
request.session["no_age"] = False request.session["no_age"] = False
request.session["not_valid_student_card_uid"] = False
if self.object.type != "BAR": if self.object.type != "BAR":
self.operator = request.user self.operator = request.user
elif self.customer_is_barman(): elif self.customer_is_barman():
@ -146,8 +146,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
action = parse_qs(request.body.decode()).get("action", [""])[0] action = parse_qs(request.body.decode()).get("action", [""])[0]
if action == "add_product": if action == "add_product":
self.add_product(request) self.add_product(request)
elif action == "add_student_card":
self.add_student_card(request)
elif action == "del_product": elif action == "del_product":
self.del_product(request) self.del_product(request)
elif action == "refill": elif action == "refill":
@ -284,23 +282,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
request.session.modified = True request.session.modified = True
return 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): def del_product(self, request):
"""Delete a product from the basket.""" """Delete a product from the basket."""
pid = parse_qs(request.body.decode())["product_id"][0] pid = parse_qs(request.body.decode())["product_id"][0]
@ -431,10 +412,10 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
product product
) )
kwargs["customer"] = self.customer 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["basket_total"] = self.sum_basket(self.request)
kwargs["refill_form"] = self.refill_form or RefillForm() 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["barmens_can_refill"] = self.object.can_refill()
kwargs["student_card"] = StudentCardFormView.get_template_data(
self.customer
).render(self.request)
return kwargs return kwargs

View File

@ -13,14 +13,18 @@
# #
# #
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpRequest
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic.edit import DeleteView, FormView from django.views.generic.edit import DeleteView, FormView
from core.utils import FormFragmentTemplateData
from core.views import CanEditMixin from core.views import CanEditMixin
from counter.forms import StudentCardForm from counter.forms import StudentCardForm
from counter.models import Customer, StudentCard from counter.models import Customer, StudentCard
from counter.utils import is_logged_in_counter
class StudentCardDeleteView(DeleteView, CanEditMixin): class StudentCardDeleteView(DeleteView, CanEditMixin):
@ -41,15 +45,38 @@ class StudentCardDeleteView(DeleteView, CanEditMixin):
class StudentCardFormView(FormView): class StudentCardFormView(FormView):
"""Add a new student card.""" """Add a new student card. This is a fragment view !"""
form_class = StudentCardForm form_class = StudentCardForm
template_name = "core/create.jinja" template_name = "counter/fragments/create_student_card.jinja"
def dispatch(self, request, *args, **kwargs): @classmethod
self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) def get_template_data(
if not StudentCard.can_create(self.customer, request.user): 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 raise PermissionDenied
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
@ -58,7 +85,11 @@ class StudentCardFormView(FormView):
StudentCard(customer=self.customer, uid=data["uid"]).save() StudentCard(customer=self.customer, uid=data["uid"]).save()
return res 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): def get_success_url(self, **kwargs):
return reverse_lazy( return self.request.path
"core:user_prefs", kwargs={"user_id": self.customer.user.pk}
)

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "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" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -218,7 +218,7 @@ msgstr "Compte"
msgid "Company" msgid "Company"
msgstr "Entreprise" 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" msgid "Other"
msgstr "Autre" msgstr "Autre"
@ -369,7 +369,7 @@ msgstr "Compte en banque : "
#: core/templates/core/user_clubs.jinja:34 #: core/templates/core/user_clubs.jinja:34
#: core/templates/core/user_clubs.jinja:63 #: core/templates/core/user_clubs.jinja:63
#: core/templates/core/user_edit.jinja:62 #: 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:35
#: counter/templates/counter/last_ops.jinja:65 #: counter/templates/counter/last_ops.jinja:65
#: election/templates/election/election_detail.jinja:191 #: election/templates/election/election_detail.jinja:191
@ -517,7 +517,7 @@ msgid "Effective amount"
msgstr "Montant effectif" msgstr "Montant effectif"
#: accounting/templates/accounting/club_account_details.jinja:36 #: accounting/templates/accounting/club_account_details.jinja:36
#: sith/settings.py:467 #: sith/settings.py:469
msgid "Closed" msgid "Closed"
msgstr "Fermé" msgstr "Fermé"
@ -650,8 +650,8 @@ msgid "Done"
msgstr "Effectuées" msgstr "Effectuées"
#: accounting/templates/accounting/journal_details.jinja:41 #: accounting/templates/accounting/journal_details.jinja:41
#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:955 #: counter/templates/counter/cash_summary_list.jinja:37
#: pedagogy/templates/pedagogy/moderation.jinja:13 #: counter/views/cash.py:87 pedagogy/templates/pedagogy/moderation.jinja:13
#: pedagogy/templates/pedagogy/uv_detail.jinja:142 #: pedagogy/templates/pedagogy/uv_detail.jinja:142
#: trombi/templates/trombi/comment.jinja:4 #: trombi/templates/trombi/comment.jinja:4
#: trombi/templates/trombi/comment.jinja:8 #: 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_godfathers_tree.jinja:85
#: core/templates/core/user_preferences.jinja:18 #: core/templates/core/user_preferences.jinja:18
#: core/templates/core/user_preferences.jinja:27 #: core/templates/core/user_preferences.jinja:27
#: core/templates/core/user_preferences.jinja:65
#: counter/templates/counter/cash_register_summary.jinja:28 #: counter/templates/counter/cash_register_summary.jinja:28
#: forum/templates/forum/reply.jinja:39 #: forum/templates/forum/reply.jinja:39
#: subscription/templates/subscription/fragments/creation_form.jinja:9 #: 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" 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" 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" msgid "Begin date"
msgstr "Date de début" 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 #: election/views.py:170 subscription/forms.py:21
msgid "End date" msgid "End date"
msgstr "Date de fin" msgstr "Date de fin"
@ -963,15 +962,16 @@ msgstr "Date de fin"
#: club/forms.py:156 club/templates/club/club_sellings.jinja:49 #: 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:17
#: core/templates/core/user_account_detail.jinja:56 #: 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" msgid "Counter"
msgstr "Comptoir" msgstr "Comptoir"
#: club/forms.py:163 counter/views.py:683 #: club/forms.py:163 counter/views/mixins.py:94
msgid "Products" msgid "Products"
msgstr "Produits" msgstr "Produits"
#: club/forms.py:168 counter/views.py:688 #: club/forms.py:168 counter/views/mixins.py:99
msgid "Archived products" msgid "Archived products"
msgstr "Produits archivés" 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" msgstr "Aucune mailing liste n'existe pour ce club"
#: club/templates/club/mailing.jinja:72 #: club/templates/club/mailing.jinja:72
#: subscription/templates/subscription/subscription.jinja:39 #: subscription/templates/subscription/subscription.jinja:38
msgid "New member" msgid "New member"
msgstr "Nouveau membre" msgstr "Nouveau membre"
@ -1426,7 +1426,8 @@ msgstr "Hebdomadaire"
msgid "Call" msgid "Call"
msgstr "Appel" 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 #: election/models.py:114 election/models.py:152 forum/models.py:256
#: forum/models.py:310 pedagogy/models.py:97 #: forum/models.py:310 pedagogy/models.py:97
msgid "title" msgid "title"
@ -1835,6 +1836,7 @@ msgid "Articles in no weekmail yet"
msgstr "Articles dans aucun weekmail" msgstr "Articles dans aucun weekmail"
#: com/templates/com/weekmail.jinja:20 com/templates/com/weekmail.jinja:49 #: com/templates/com/weekmail.jinja:20 com/templates/com/weekmail.jinja:49
#: core/templates/core/macros.jinja:301
msgid "Content" msgid "Content"
msgstr "Contenu" msgstr "Contenu"
@ -2505,7 +2507,7 @@ msgstr "Photos"
#: eboutic/templates/eboutic/eboutic_main.jinja:22 #: eboutic/templates/eboutic/eboutic_main.jinja:22
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:16 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:16
#: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 #: 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" msgid "Eboutic"
msgstr "Eboutic" msgstr "Eboutic"
@ -2583,7 +2585,7 @@ msgstr "Confirmation"
#: core/templates/core/delete_confirm.jinja:20 #: core/templates/core/delete_confirm.jinja:20
#: core/templates/core/file_delete_confirm.jinja:46 #: 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 #: sas/templates/sas/ask_picture_removal.jinja:20
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
@ -3042,11 +3044,11 @@ msgid "Eboutic invoices"
msgstr "Facture eboutic" msgstr "Facture eboutic"
#: core/templates/core/user_account.jinja:54 #: 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" msgid "Etickets"
msgstr "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" msgid "User has no account"
msgstr "L'utilisateur n'a pas de compte" msgstr "L'utilisateur n'a pas de compte"
@ -3137,7 +3139,7 @@ msgstr "Non cotisant"
#: core/templates/core/user_detail.jinja:162 #: core/templates/core/user_detail.jinja:162
#: subscription/templates/subscription/subscription.jinja:6 #: subscription/templates/subscription/subscription.jinja:6
#: subscription/templates/subscription/subscription.jinja:37 #: subscription/templates/subscription/subscription.jinja:36
msgid "New subscription" msgid "New subscription"
msgstr "Nouvelle cotisation" msgstr "Nouvelle cotisation"
@ -3295,15 +3297,7 @@ msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s."
msgid "Go to my Trombi tools" msgid "Go to my Trombi tools"
msgstr "Allez à mes outils de Trombi" msgstr "Allez à mes outils de Trombi"
#: core/templates/core/user_preferences.jinja:39 #: core/templates/core/user_preferences.jinja:49
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
msgid "" msgid ""
"You can add a card by asking at a counter or add it yourself here. If you " "You can add a card by asking at a counter or add it yourself here. If you "
"want to manually\n" "want to manually\n"
@ -3377,8 +3371,8 @@ msgstr "Cotisations"
msgid "Subscription stats" msgid "Subscription stats"
msgstr "Statistiques de cotisation" msgstr "Statistiques de cotisation"
#: core/templates/core/user_tools.jinja:48 counter/forms.py:176 #: core/templates/core/user_tools.jinja:48 counter/forms.py:166
#: counter/views.py:678 #: counter/views/mixins.py:89
msgid "Counters" msgid "Counters"
msgstr "Comptoirs" msgstr "Comptoirs"
@ -3395,12 +3389,13 @@ msgid "Product types management"
msgstr "Gestion des types de produit" msgstr "Gestion des types de produit"
#: core/templates/core/user_tools.jinja:56 #: 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" msgid "Cash register summaries"
msgstr "Relevés de caisse" msgstr "Relevés de caisse"
#: core/templates/core/user_tools.jinja:57 #: 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" msgid "Invoices call"
msgstr "Appels à facture" msgstr "Appels à facture"
@ -3548,7 +3543,7 @@ msgstr "Parrain / Marraine"
msgid "Godchild" msgid "Godchild"
msgstr "Fillot / Fillote" 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" msgid "Select user"
msgstr "Choisir un utilisateur" msgstr "Choisir un utilisateur"
@ -3601,15 +3596,15 @@ msgstr "Galaxie"
msgid "counter" msgid "counter"
msgstr "comptoir" msgstr "comptoir"
#: counter/forms.py:63 #: counter/forms.py:61
msgid "This UID is invalid" msgid "This UID is invalid"
msgstr "Cet UID est invalide" msgstr "Cet UID est invalide"
#: counter/forms.py:111 #: counter/forms.py:109
msgid "User not found" msgid "User not found"
msgstr "Utilisateur non trouvé" 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" msgid "Your AE account has been emptied"
msgstr "Votre compte AE a été vidé" msgstr "Votre compte AE a été vidé"
@ -3637,7 +3632,7 @@ msgstr "client"
msgid "customers" msgid "customers"
msgstr "clients" msgstr "clients"
#: counter/models.py:110 counter/views.py:261 #: counter/models.py:110 counter/views/click.py:66
msgid "Not enough money" msgid "Not enough money"
msgstr "Solde insuffisant" msgstr "Solde insuffisant"
@ -3777,8 +3772,8 @@ msgstr "quantité"
msgid "Sith account" msgid "Sith account"
msgstr "Compte utilisateur" msgstr "Compte utilisateur"
#: counter/models.py:797 sith/settings.py:413 sith/settings.py:418 #: counter/models.py:797 sith/settings.py:415 sith/settings.py:420
#: sith/settings.py:438 #: sith/settings.py:440
msgid "Credit card" msgid "Credit card"
msgstr "Carte bancaire" msgstr "Carte bancaire"
@ -3910,7 +3905,8 @@ msgstr "Liste des relevés de caisse"
msgid "Theoric sums" msgid "Theoric sums"
msgstr "Sommes théoriques" 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" msgid "Emptied"
msgstr "Coffre vidé" msgstr "Coffre vidé"
@ -3922,17 +3918,14 @@ msgstr "oui"
msgid "There is no cash register summary in this website." msgid "There is no cash register summary in this website."
msgstr "Il n'y a pas de relevé de caisse dans ce site web." msgstr "Il n'y a pas de relevé de caisse dans ce site web."
#: counter/templates/counter/counter_click.jinja:35 #: counter/templates/counter/counter_click.jinja:46
msgid "Add a student card" #: launderette/templates/launderette/launderette_admin.jinja:8
msgstr "Ajouter une carte étudiante" msgid "Selling"
msgstr "Vente"
#: counter/templates/counter/counter_click.jinja:38 #: counter/templates/counter/counter_click.jinja:57
msgid "This is not a valid student card UID" #: counter/templates/counter/counter_click.jinja:122
msgstr "Ce n'est pas un UID de carte étudiante valide" #: counter/templates/counter/fragments/create_student_card.jinja:10
#: counter/templates/counter/counter_click.jinja:40
#: counter/templates/counter/counter_click.jinja:67
#: counter/templates/counter/counter_click.jinja:132
#: counter/templates/counter/invoices_call.jinja:16 #: counter/templates/counter/invoices_call.jinja:16
#: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_admin.jinja:35
#: launderette/templates/launderette/launderette_click.jinja:13 #: launderette/templates/launderette/launderette_click.jinja:13
@ -3941,29 +3934,16 @@ msgstr "Ce n'est pas un UID de carte étudiante valide"
msgid "Go" msgid "Go"
msgstr "Valider" msgstr "Valider"
#: counter/templates/counter/counter_click.jinja:42 #: counter/templates/counter/counter_click.jinja:64
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
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:19 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:19
msgid "Basket: " msgid "Basket: "
msgstr "Panier : " msgstr "Panier : "
#: counter/templates/counter/counter_click.jinja:115 #: counter/templates/counter/counter_click.jinja:105
msgid "Finish" msgid "Finish"
msgstr "Terminer" msgstr "Terminer"
#: counter/templates/counter/counter_click.jinja:125 #: counter/templates/counter/counter_click.jinja:115
#: counter/templates/counter/refilling_list.jinja:9 #: counter/templates/counter/refilling_list.jinja:9
msgid "Refilling" msgid "Refilling"
msgstr "Rechargement" msgstr "Rechargement"
@ -4047,6 +4027,18 @@ msgstr "Nouveau eticket"
msgid "There is no eticket in this website." msgid "There is no eticket in this website."
msgstr "Il n'y a pas de eticket sur ce site web." 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 #: counter/templates/counter/invoices_call.jinja:8
#, python-format #, python-format
msgid "Invoices call for %(date)s" msgid "Invoices call for %(date)s"
@ -4219,104 +4211,104 @@ msgstr "Temps"
msgid "Top 100 barman %(counter_name)s (all semesters)" msgid "Top 100 barman %(counter_name)s (all semesters)"
msgstr "Top 100 barman %(counter_name)s (tous les semestres)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)"
#: counter/views.py:147 #: counter/views/cash.py:45
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
msgid "10 cents" msgid "10 cents"
msgstr "10 centimes" msgstr "10 centimes"
#: counter/views.py:914 #: counter/views/cash.py:46
msgid "20 cents" msgid "20 cents"
msgstr "20 centimes" msgstr "20 centimes"
#: counter/views.py:915 #: counter/views/cash.py:47
msgid "50 cents" msgid "50 cents"
msgstr "50 centimes" msgstr "50 centimes"
#: counter/views.py:916 #: counter/views/cash.py:48
msgid "1 euro" msgid "1 euro"
msgstr "1 €" msgstr "1 €"
#: counter/views.py:917 #: counter/views/cash.py:49
msgid "2 euros" msgid "2 euros"
msgstr "2 €" msgstr "2 €"
#: counter/views.py:918 #: counter/views/cash.py:50
msgid "5 euros" msgid "5 euros"
msgstr "5 €" msgstr "5 €"
#: counter/views.py:919 #: counter/views/cash.py:51
msgid "10 euros" msgid "10 euros"
msgstr "10 €" msgstr "10 €"
#: counter/views.py:920 #: counter/views/cash.py:52
msgid "20 euros" msgid "20 euros"
msgstr "20 €" msgstr "20 €"
#: counter/views.py:921 #: counter/views/cash.py:53
msgid "50 euros" msgid "50 euros"
msgstr "50 €" msgstr "50 €"
#: counter/views.py:923 #: counter/views/cash.py:55
msgid "100 euros" msgid "100 euros"
msgstr "100 €" msgstr "100 €"
#: counter/views.py:926 counter/views.py:932 counter/views.py:938 #: counter/views/cash.py:58 counter/views/cash.py:64 counter/views/cash.py:70
#: counter/views.py:944 counter/views.py:950 #: counter/views/cash.py:76 counter/views/cash.py:82
msgid "Check amount" msgid "Check amount"
msgstr "Montant du chèque" msgstr "Montant du chèque"
#: counter/views.py:929 counter/views.py:935 counter/views.py:941 #: counter/views/cash.py:61 counter/views/cash.py:67 counter/views/cash.py:73
#: counter/views.py:947 counter/views.py:953 #: counter/views/cash.py:79 counter/views/cash.py:85
msgid "Check quantity" msgid "Check quantity"
msgstr "Nombre de chèque" 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)" msgid "people(s)"
msgstr "personne(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 #: eboutic/forms.py:88
msgid "The request was badly formatted." msgid "The request was badly formatted."
msgstr "La requête a été mal formatée." msgstr "La requête a été mal formatée."
@ -4894,12 +4886,12 @@ msgid "Washing and drying"
msgstr "Lavage et séchage" msgstr "Lavage et séchage"
#: launderette/templates/launderette/launderette_book.jinja:27 #: launderette/templates/launderette/launderette_book.jinja:27
#: sith/settings.py:656 #: sith/settings.py:658
msgid "Washing" msgid "Washing"
msgstr "Lavage" msgstr "Lavage"
#: launderette/templates/launderette/launderette_book.jinja:31 #: launderette/templates/launderette/launderette_book.jinja:31
#: sith/settings.py:656 #: sith/settings.py:658
msgid "Drying" msgid "Drying"
msgstr "Séchage" msgstr "Séchage"
@ -5414,380 +5406,380 @@ msgstr "Personne(s)"
msgid "Identify users on pictures" msgid "Identify users on pictures"
msgstr "Identifiez les utilisateurs sur les photos" 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" msgid "English"
msgstr "Anglais" msgstr "Anglais"
#: sith/settings.py:254 sith/settings.py:474 #: sith/settings.py:253 sith/settings.py:476
msgid "French" msgid "French"
msgstr "Français" msgstr "Français"
#: sith/settings.py:394 #: sith/settings.py:396
msgid "TC" msgid "TC"
msgstr "TC" msgstr "TC"
#: sith/settings.py:395 #: sith/settings.py:397
msgid "IMSI" msgid "IMSI"
msgstr "IMSI" msgstr "IMSI"
#: sith/settings.py:396 #: sith/settings.py:398
msgid "IMAP" msgid "IMAP"
msgstr "IMAP" msgstr "IMAP"
#: sith/settings.py:397 #: sith/settings.py:399
msgid "INFO" msgid "INFO"
msgstr "INFO" msgstr "INFO"
#: sith/settings.py:398 #: sith/settings.py:400
msgid "GI" msgid "GI"
msgstr "GI" msgstr "GI"
#: sith/settings.py:399 sith/settings.py:485 #: sith/settings.py:401 sith/settings.py:487
msgid "E" msgid "E"
msgstr "E" msgstr "E"
#: sith/settings.py:400 #: sith/settings.py:402
msgid "EE" msgid "EE"
msgstr "EE" msgstr "EE"
#: sith/settings.py:401 #: sith/settings.py:403
msgid "GESC" msgid "GESC"
msgstr "GESC" msgstr "GESC"
#: sith/settings.py:402 #: sith/settings.py:404
msgid "GMC" msgid "GMC"
msgstr "GMC" msgstr "GMC"
#: sith/settings.py:403 #: sith/settings.py:405
msgid "MC" msgid "MC"
msgstr "MC" msgstr "MC"
#: sith/settings.py:404 #: sith/settings.py:406
msgid "EDIM" msgid "EDIM"
msgstr "EDIM" msgstr "EDIM"
#: sith/settings.py:405 #: sith/settings.py:407
msgid "Humanities" msgid "Humanities"
msgstr "Humanités" msgstr "Humanités"
#: sith/settings.py:406 #: sith/settings.py:408
msgid "N/A" msgid "N/A"
msgstr "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" msgid "Check"
msgstr "Chèque" 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" msgid "Cash"
msgstr "Espèces" msgstr "Espèces"
#: sith/settings.py:412 #: sith/settings.py:414
msgid "Transfert" msgid "Transfert"
msgstr "Virement" msgstr "Virement"
#: sith/settings.py:425 #: sith/settings.py:427
msgid "Belfort" msgid "Belfort"
msgstr "Belfort" msgstr "Belfort"
#: sith/settings.py:426 #: sith/settings.py:428
msgid "Sevenans" msgid "Sevenans"
msgstr "Sevenans" msgstr "Sevenans"
#: sith/settings.py:427 #: sith/settings.py:429
msgid "Montbéliard" msgid "Montbéliard"
msgstr "Montbéliard" msgstr "Montbéliard"
#: sith/settings.py:455 #: sith/settings.py:457
msgid "Free" msgid "Free"
msgstr "Libre" msgstr "Libre"
#: sith/settings.py:456 #: sith/settings.py:458
msgid "CS" msgid "CS"
msgstr "CS" msgstr "CS"
#: sith/settings.py:457 #: sith/settings.py:459
msgid "TM" msgid "TM"
msgstr "TM" msgstr "TM"
#: sith/settings.py:458 #: sith/settings.py:460
msgid "OM" msgid "OM"
msgstr "OM" msgstr "OM"
#: sith/settings.py:459 #: sith/settings.py:461
msgid "QC" msgid "QC"
msgstr "QC" msgstr "QC"
#: sith/settings.py:460 #: sith/settings.py:462
msgid "EC" msgid "EC"
msgstr "EC" msgstr "EC"
#: sith/settings.py:461 #: sith/settings.py:463
msgid "RN" msgid "RN"
msgstr "RN" msgstr "RN"
#: sith/settings.py:462 #: sith/settings.py:464
msgid "ST" msgid "ST"
msgstr "ST" msgstr "ST"
#: sith/settings.py:463 #: sith/settings.py:465
msgid "EXT" msgid "EXT"
msgstr "EXT" msgstr "EXT"
#: sith/settings.py:468 #: sith/settings.py:470
msgid "Autumn" msgid "Autumn"
msgstr "Automne" msgstr "Automne"
#: sith/settings.py:469 #: sith/settings.py:471
msgid "Spring" msgid "Spring"
msgstr "Printemps" msgstr "Printemps"
#: sith/settings.py:470 #: sith/settings.py:472
msgid "Autumn and spring" msgid "Autumn and spring"
msgstr "Automne et printemps" msgstr "Automne et printemps"
#: sith/settings.py:476 #: sith/settings.py:478
msgid "German" msgid "German"
msgstr "Allemand" msgstr "Allemand"
#: sith/settings.py:477 #: sith/settings.py:479
msgid "Spanish" msgid "Spanish"
msgstr "Espagnol" msgstr "Espagnol"
#: sith/settings.py:481 #: sith/settings.py:483
msgid "A" msgid "A"
msgstr "A" msgstr "A"
#: sith/settings.py:482 #: sith/settings.py:484
msgid "B" msgid "B"
msgstr "B" msgstr "B"
#: sith/settings.py:483 #: sith/settings.py:485
msgid "C" msgid "C"
msgstr "C" msgstr "C"
#: sith/settings.py:484 #: sith/settings.py:486
msgid "D" msgid "D"
msgstr "D" msgstr "D"
#: sith/settings.py:486 #: sith/settings.py:488
msgid "FX" msgid "FX"
msgstr "FX" msgstr "FX"
#: sith/settings.py:487 #: sith/settings.py:489
msgid "F" msgid "F"
msgstr "F" msgstr "F"
#: sith/settings.py:488 #: sith/settings.py:490
msgid "Abs" msgid "Abs"
msgstr "Abs" msgstr "Abs"
#: sith/settings.py:492 #: sith/settings.py:494
msgid "Selling deletion" msgid "Selling deletion"
msgstr "Suppression de vente" msgstr "Suppression de vente"
#: sith/settings.py:493 #: sith/settings.py:495
msgid "Refilling deletion" msgid "Refilling deletion"
msgstr "Suppression de rechargement" msgstr "Suppression de rechargement"
#: sith/settings.py:537 #: sith/settings.py:539
msgid "One semester" msgid "One semester"
msgstr "Un semestre, 20 €" msgstr "Un semestre, 20 €"
#: sith/settings.py:538 #: sith/settings.py:540
msgid "Two semesters" msgid "Two semesters"
msgstr "Deux semestres, 35 €" msgstr "Deux semestres, 35 €"
#: sith/settings.py:540 #: sith/settings.py:542
msgid "Common core cursus" msgid "Common core cursus"
msgstr "Cursus tronc commun, 60 €" msgstr "Cursus tronc commun, 60 €"
#: sith/settings.py:544 #: sith/settings.py:546
msgid "Branch cursus" msgid "Branch cursus"
msgstr "Cursus branche, 60 €" msgstr "Cursus branche, 60 €"
#: sith/settings.py:545 #: sith/settings.py:547
msgid "Alternating cursus" msgid "Alternating cursus"
msgstr "Cursus alternant, 30 €" msgstr "Cursus alternant, 30 €"
#: sith/settings.py:546 #: sith/settings.py:548
msgid "Honorary member" msgid "Honorary member"
msgstr "Membre honoraire, 0 €" msgstr "Membre honoraire, 0 €"
#: sith/settings.py:547 #: sith/settings.py:549
msgid "Assidu member" msgid "Assidu member"
msgstr "Membre d'Assidu, 0 €" msgstr "Membre d'Assidu, 0 €"
#: sith/settings.py:548 #: sith/settings.py:550
msgid "Amicale/DOCEO member" msgid "Amicale/DOCEO member"
msgstr "Membre de l'Amicale/DOCEO, 0 €" msgstr "Membre de l'Amicale/DOCEO, 0 €"
#: sith/settings.py:549 #: sith/settings.py:551
msgid "UT network member" msgid "UT network member"
msgstr "Cotisant du réseau UT, 0 €" msgstr "Cotisant du réseau UT, 0 €"
#: sith/settings.py:550 #: sith/settings.py:552
msgid "CROUS member" msgid "CROUS member"
msgstr "Membres du CROUS, 0 €" msgstr "Membres du CROUS, 0 €"
#: sith/settings.py:551 #: sith/settings.py:553
msgid "Sbarro/ESTA member" msgid "Sbarro/ESTA member"
msgstr "Membre de Sbarro ou de l'ESTA, 20 €" msgstr "Membre de Sbarro ou de l'ESTA, 20 €"
#: sith/settings.py:553 #: sith/settings.py:555
msgid "One semester Welcome Week" msgid "One semester Welcome Week"
msgstr "Un semestre Welcome Week" msgstr "Un semestre Welcome Week"
#: sith/settings.py:557 #: sith/settings.py:559
msgid "One month for free" msgid "One month for free"
msgstr "Un mois gratuit" msgstr "Un mois gratuit"
#: sith/settings.py:558 #: sith/settings.py:560
msgid "Two months for free" msgid "Two months for free"
msgstr "Deux mois gratuits" msgstr "Deux mois gratuits"
#: sith/settings.py:559 #: sith/settings.py:561
msgid "Eurok's volunteer" msgid "Eurok's volunteer"
msgstr "Bénévole Eurockéennes" msgstr "Bénévole Eurockéennes"
#: sith/settings.py:561 #: sith/settings.py:563
msgid "Six weeks for free" msgid "Six weeks for free"
msgstr "6 semaines gratuites" msgstr "6 semaines gratuites"
#: sith/settings.py:565 #: sith/settings.py:567
msgid "One day" msgid "One day"
msgstr "Un jour" msgstr "Un jour"
#: sith/settings.py:566 #: sith/settings.py:568
msgid "GA staff member" msgid "GA staff member"
msgstr "Membre staff GA (2 semaines), 1 €" msgstr "Membre staff GA (2 semaines), 1 €"
#: sith/settings.py:569 #: sith/settings.py:571
msgid "One semester (-20%)" msgid "One semester (-20%)"
msgstr "Un semestre (-20%), 12 €" msgstr "Un semestre (-20%), 12 €"
#: sith/settings.py:574 #: sith/settings.py:576
msgid "Two semesters (-20%)" msgid "Two semesters (-20%)"
msgstr "Deux semestres (-20%), 22 €" msgstr "Deux semestres (-20%), 22 €"
#: sith/settings.py:579 #: sith/settings.py:581
msgid "Common core cursus (-20%)" msgid "Common core cursus (-20%)"
msgstr "Cursus tronc commun (-20%), 36 €" msgstr "Cursus tronc commun (-20%), 36 €"
#: sith/settings.py:584 #: sith/settings.py:586
msgid "Branch cursus (-20%)" msgid "Branch cursus (-20%)"
msgstr "Cursus branche (-20%), 36 €" msgstr "Cursus branche (-20%), 36 €"
#: sith/settings.py:589 #: sith/settings.py:591
msgid "Alternating cursus (-20%)" msgid "Alternating cursus (-20%)"
msgstr "Cursus alternant (-20%), 24 €" msgstr "Cursus alternant (-20%), 24 €"
#: sith/settings.py:595 #: sith/settings.py:597
msgid "One year for free(CA offer)" msgid "One year for free(CA offer)"
msgstr "Une année offerte (Offre CA)" msgstr "Une année offerte (Offre CA)"
#: sith/settings.py:615 #: sith/settings.py:617
msgid "President" msgid "President"
msgstr "Président⸱e" msgstr "Président⸱e"
#: sith/settings.py:616 #: sith/settings.py:618
msgid "Vice-President" msgid "Vice-President"
msgstr "Vice-Président⸱e" msgstr "Vice-Président⸱e"
#: sith/settings.py:617 #: sith/settings.py:619
msgid "Treasurer" msgid "Treasurer"
msgstr "Trésorier⸱e" msgstr "Trésorier⸱e"
#: sith/settings.py:618 #: sith/settings.py:620
msgid "Communication supervisor" msgid "Communication supervisor"
msgstr "Responsable communication" msgstr "Responsable communication"
#: sith/settings.py:619 #: sith/settings.py:621
msgid "Secretary" msgid "Secretary"
msgstr "Secrétaire" msgstr "Secrétaire"
#: sith/settings.py:620 #: sith/settings.py:622
msgid "IT supervisor" msgid "IT supervisor"
msgstr "Responsable info" msgstr "Responsable info"
#: sith/settings.py:621 #: sith/settings.py:623
msgid "Board member" msgid "Board member"
msgstr "Membre du bureau" msgstr "Membre du bureau"
#: sith/settings.py:622 #: sith/settings.py:624
msgid "Active member" msgid "Active member"
msgstr "Membre actif⸱ve" msgstr "Membre actif⸱ve"
#: sith/settings.py:623 #: sith/settings.py:625
msgid "Curious" msgid "Curious"
msgstr "Curieux⸱euse" msgstr "Curieux⸱euse"
#: sith/settings.py:660 #: sith/settings.py:662
msgid "A new poster needs to be moderated" msgid "A new poster needs to be moderated"
msgstr "Une nouvelle affiche a besoin d'être modérée" 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" msgid "A new mailing list needs to be moderated"
msgstr "Une nouvelle mailing list a besoin d'être modérée" 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" msgid "A new pedagogy comment has been signaled for moderation"
msgstr "" msgstr ""
"Un nouveau commentaire de la pédagogie a été signalé pour la modération" "Un nouveau commentaire de la pédagogie a été signalé pour la modération"
#: sith/settings.py:666 #: sith/settings.py:668
#, python-format #, python-format
msgid "There are %s fresh news to be moderated" msgid "There are %s fresh news to be moderated"
msgstr "Il y a %s nouvelles toutes fraîches à modérer" 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" msgid "New files to be moderated"
msgstr "Nouveaux fichiers à modérer" msgstr "Nouveaux fichiers à modérer"
#: sith/settings.py:668 #: sith/settings.py:670
#, python-format #, python-format
msgid "There are %s pictures to be moderated in the SAS" msgid "There are %s pictures to be moderated in the SAS"
msgstr "Il y a %s photos à modérer dans le 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" msgid "You've been identified on some pictures"
msgstr "Vous avez été identifié sur des photos" msgstr "Vous avez été identifié sur des photos"
#: sith/settings.py:670 #: sith/settings.py:672
#, python-format #, python-format
msgid "You just refilled of %s" msgid "You just refilled of %s"
msgstr "Vous avez rechargé votre compte de %s" msgstr "Vous avez rechargé votre compte de %s"
#: sith/settings.py:671 #: sith/settings.py:673
#, python-format #, python-format
msgid "You just bought %s" msgid "You just bought %s"
msgstr "Vous avez acheté %s" msgstr "Vous avez acheté %s"
#: sith/settings.py:672 #: sith/settings.py:674
msgid "You have a notification" msgid "You have a notification"
msgstr "Vous avez une notification" msgstr "Vous avez une notification"
#: sith/settings.py:684 #: sith/settings.py:686
msgid "Success!" msgid "Success!"
msgstr "Succès !" msgstr "Succès !"
#: sith/settings.py:685 #: sith/settings.py:687
msgid "Fail!" msgid "Fail!"
msgstr "Échec !" msgstr "Échec !"
#: sith/settings.py:686 #: sith/settings.py:688
msgid "You successfully posted an article in the Weekmail" msgid "You successfully posted an article in the Weekmail"
msgstr "Article posté avec succès dans le 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" msgid "You successfully edited an article in the Weekmail"
msgstr "Article édité avec succès dans le Weekmail" msgstr "Article édité avec succès dans le Weekmail"
#: sith/settings.py:688 #: sith/settings.py:690
msgid "You successfully sent the Weekmail" msgid "You successfully sent the Weekmail"
msgstr "Weekmail envoyé avec succès" msgstr "Weekmail envoyé avec succès"
#: sith/settings.py:696 #: sith/settings.py:698
msgid "AE tee-shirt" msgid "AE tee-shirt"
msgstr "Tee-shirt AE" 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" msgid "Subscription created for %(user)s"
msgstr "Cotisation créée pour %(user)s" msgstr "Cotisation créée pour %(user)s"
#: subscription/templates/subscription/fragments/creation_success.jinja:8 #: subscription/templates/subscription/fragments/creation_success.jinja:19
#, 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
msgid "Go to user profile" msgid "Go to user profile"
msgstr "Voir le profil de l'utilisateur" 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" msgid "Create another subscription"
msgstr "Créer une nouvelle cotisation" msgstr "Créer une nouvelle cotisation"
#: subscription/templates/subscription/subscription.jinja
msgid "Existing member"
msgstr "Membre existant"
#: subscription/templates/subscription/stats.jinja:27 #: subscription/templates/subscription/stats.jinja:27
msgid "Total subscriptions" msgid "Total subscriptions"
msgstr "Cotisations totales" msgstr "Cotisations totales"
@ -5857,6 +5836,10 @@ msgstr "Cotisations totales"
msgid "Subscriptions by type" msgid "Subscriptions by type"
msgstr "Cotisations par type" msgstr "Cotisations par type"
#: subscription/templates/subscription/subscription.jinja:38
msgid "Existing member"
msgstr "Membre existant"
#: trombi/models.py:55 #: trombi/models.py:55
msgid "subscription deadline" msgid "subscription deadline"
msgstr "fin des inscriptions" msgstr "fin des inscriptions"

View File

@ -1,10 +1,10 @@
from django.conf import settings from django.conf import settings
from django.core.cache import cache
from django.db import transaction from django.db import transaction
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from model_bakery import baker from model_bakery import baker
from model_bakery.recipe import Recipe from model_bakery.recipe import Recipe
from pytest_django.asserts import assertNumQueries
from core.baker_recipes import old_subscriber_user, subscriber_user from core.baker_recipes import old_subscriber_user, subscriber_user
from core.models import RealGroup, SithFile, User from core.models import RealGroup, SithFile, User
@ -128,9 +128,11 @@ class TestPictureSearch(TestSas):
def test_num_queries(self): def test_num_queries(self):
"""Test that the number of queries is stable.""" """Test that the number of queries is stable."""
self.client.force_login(subscriber_user.make()) 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 # 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 fetch the pictures
# 1 request to count the total number of items in the pagination # 1 request to count the total number of items in the pagination
self.client.get(self.url) self.client.get(self.url)