diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 9e261bba..7098101a 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -69,7 +69,7 @@ class Command(BaseCommand): # sqlite doesn't support this operation return sqlcmd = StringIO() - call_command("sqlsequencereset", *args, stdout=sqlcmd) + call_command("sqlsequencereset", "--no-color", *args, stdout=sqlcmd) cursor = connection.cursor() cursor.execute(sqlcmd.getvalue()) @@ -137,11 +137,10 @@ class Command(BaseCommand): ) self.reset_index("club") + for bar_id, bar_name in settings.SITH_COUNTER_BARS: + Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR").save() + self.reset_index("counter") counters = [ - *[ - Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR") - for bar_id, bar_name in settings.SITH_COUNTER_BARS - ], Counter(name="Eboutic", club=main_club, type="EBOUTIC"), Counter(name="AE", club=main_club, type="OFFICE"), Counter(name="Vidage comptes AE", club=main_club, type="OFFICE"), diff --git a/core/templates/core/user_preferences.jinja b/core/templates/core/user_preferences.jinja index 722e7c44..bf5189ae 100644 --- a/core/templates/core/user_preferences.jinja +++ b/core/templates/core/user_preferences.jinja @@ -35,8 +35,8 @@ {% endif %} - {% if student_card %} - {{ student_card }} + {% if student_card_fragment %} + {{ student_card_fragment }}

{% 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 %} diff --git a/core/views/user.py b/core/views/user.py index b758590a..9f724fca 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -571,7 +571,7 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): if not hasattr(self.object, "trombi_user"): kwargs["trombi_form"] = UserTrombiForm() if hasattr(self.object, "customer"): - kwargs["student_card"] = StudentCardFormView.get_template_data( + kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( self.object.customer ).render(self.request) return kwargs diff --git a/counter/forms.py b/counter/forms.py index 538b387c..0a8bb3be 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -50,9 +50,7 @@ class StudentCardForm(forms.ModelForm): class Meta: model = StudentCard fields = ["uid"] - widgets = { - "uid": NFCTextInput, - } + widgets = {"uid": NFCTextInput} def clean(self): cleaned_data = super().clean() diff --git a/counter/migrations/0026_alter_studentcard_customer.py b/counter/migrations/0026_alter_studentcard_customer.py new file mode 100644 index 00000000..f1f5cd49 --- /dev/null +++ b/counter/migrations/0026_alter_studentcard_customer.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2.17 on 2024-12-08 13:30 +from operator import attrgetter + +import django.db.models.deletion +from django.db import migrations, models +from django.db.migrations.state import StateApps +from django.db.models import Count + + +def delete_duplicates(apps: StateApps, schema_editor): + """Delete cards of users with more than one student cards. + + For all users who have more than one registered student card, all + the cards except the last one are deleted. + """ + Customer = apps.get_model("counter", "Customer") + StudentCard = apps.get_model("counter", "StudentCard") + customers = ( + Customer.objects.annotate(nb_cards=Count("student_cards")) + .filter(nb_cards__gt=1) + .prefetch_related("student_cards") + ) + to_delete = [ + card.id + for customer in customers + for card in sorted(customer.student_cards.all(), key=attrgetter("id"))[:-1] + ] + StudentCard.objects.filter(id__in=to_delete).delete() + + +class Migration(migrations.Migration): + dependencies = [("counter", "0025_remove_product_parent_product_and_more")] + + operations = [ + migrations.RunPython(delete_duplicates, migrations.RunPython.noop), + migrations.AlterField( + model_name="studentcard", + name="customer", + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="student_card", + to="counter.customer", + verbose_name="student card", + ), + ), + migrations.AlterModelOptions( + name="studentcard", + options={ + "verbose_name": "student card", + "verbose_name_plural": "student cards", + }, + ), + ] diff --git a/counter/models.py b/counter/models.py index cf285839..292cd59f 100644 --- a/counter/models.py +++ b/counter/models.py @@ -1138,20 +1138,22 @@ class StudentCard(models.Model): uid = models.CharField( _("uid"), max_length=UID_SIZE, unique=True, validators=[MinLengthValidator(4)] ) - customer = models.ForeignKey( + customer = models.OneToOneField( Customer, - related_name="student_cards", - verbose_name=_("student cards"), - null=False, - blank=False, + related_name="student_card", + verbose_name=_("student card"), on_delete=models.CASCADE, ) + class Meta: + verbose_name = _("student card") + verbose_name_plural = _("student cards") + def __str__(self): return self.uid @staticmethod - def is_valid(uid): + def is_valid(uid: str) -> bool: return ( (uid.isupper() or uid.isnumeric()) and len(uid) == StudentCard.UID_SIZE diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index 7c36b01b..4b9e1898 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -31,7 +31,7 @@

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

{% if counter.type == 'BAR' %} - {{ student_card }} + {{ student_card_fragment }} {% endif %} diff --git a/counter/templates/counter/fragments/create_student_card.jinja b/counter/templates/counter/fragments/create_student_card.jinja index ab846c55..f8e59d24 100644 --- a/counter/templates/counter/fragments/create_student_card.jinja +++ b/counter/templates/counter/fragments/create_student_card.jinja @@ -1,29 +1,22 @@
-

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

-
- {% csrf_token %} - {{ form.as_p() }} - - -
-
{% trans %}Registered cards{% endtrans %}
- {% if student_cards %} - - - {% else %} +

{% trans %}Student card{% endtrans %}

+ {% if not customer.student_card %} +
+ {% csrf_token %} + {{ form.as_p() }} + +
{% trans %}No student card registered.{% endtrans %} + {% else %} +

+ {% trans %}Registered{% endtrans %}   -   + + {% trans %}Delete{% endtrans %} + +

{% endif %}
diff --git a/counter/tests/test_customer.py b/counter/tests/test_customer.py index f7e599e6..b861a97e 100644 --- a/counter/tests/test_customer.py +++ b/counter/tests/test_customer.py @@ -1,3 +1,4 @@ +import itertools import json import string from datetime import timedelta @@ -175,7 +176,6 @@ class TestStudentCard(TestCase): @classmethod def setUpTestData(cls): 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) @@ -205,6 +205,22 @@ class TestStudentCard(TestCase): {"username": self.barmen.username, "password": "plop"}, ) + def invalid_uids(self) -> list[tuple[str, str]]: + """Return a list of invalid uids, with the associated error message""" + return [ + ("8B90734A802A8", ""), # too short + ( + "8B90734A802A8FA", + "Assurez-vous que cette valeur comporte au plus 14 caractères (actuellement 15).", + ), # too long + ("8b90734a802a9f", ""), # has lowercases + (" " * 14, "Ce champ est obligatoire."), # empty + ( + self.customer.customer.student_card.uid, + "Un objet Carte étudiante avec ce champ Uid existe déjà.", + ), + ] + def test_search_user_with_student_card(self): response = self.client.post( reverse("counter:details", args=[self.counter.id]), @@ -213,396 +229,144 @@ class TestStudentCard(TestCase): assert response.url == reverse( "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.customer.id}, + kwargs={"counter_id": self.counter.id, "user_id": self.customer.pk}, ) def test_add_student_card_from_counter(self): - # Test card with mixed letters and numbers - response = self.client.post( - reverse( - "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, - }, - ), - ) - 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: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, - }, - ), - ) - 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: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, - }, - ), - ) - assert response.status_code == 302 - self.assertContains(self.client.get(response.url), text="ABCAAAFAAFAAAB") + for uid in ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"]: + customer = subscriber_user.make().customer + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": customer.pk} + ), + {"uid": uid}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": customer.pk}, + ), + ) + assert response.status_code == 302 + customer.refresh_from_db() + assert hasattr(customer, "student_card") + assert customer.student_card.uid == uid def test_add_student_card_from_counter_fail(self): - # UID too short - response = self.client.post( - reverse( - "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, - }, - ), - ) - self.assertContains(response, text="Cet UID est invalide") - - # UID too long - response = self.client.post( - reverse( - "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, - }, - ), - ) - self.assertContains(response, text="Cet UID est invalide") - self.assertContains( - 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: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, - }, - ), - ) - self.assertContains(response, text="Cet UID est invalide") - self.assertContains( - response, text="Un objet Student card avec ce champ Uid existe déjà." - ) - - # Test with lowercase - response = self.client.post( - reverse( - "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, - }, - ), - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with white spaces - response = self.client.post( - reverse( - "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, - }, - ), - ) - self.assertContains(response, text="Cet UID est invalide") - self.assertContains(response, text="Ce champ est obligatoire.") + customer = subscriber_user.make().customer + for uid, error_msg in self.invalid_uids(): + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": customer.pk} + ), + {"uid": uid}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": customer.pk}, + ), + ) + self.assertContains(response, text="Cet UID est invalide") + self.assertContains(response, text=error_msg) + customer.refresh_from_db() + assert not hasattr(customer, "student_card") 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}, - ) + barman = subscriber_user.make() + self.counter.sellers.add(barman) + customer = self.customer.customer + # There is someone logged to a counter + # with the client of this TestCase instance, + # so we create a new client, in order to check + # that using a client not logged to a counter + # where another client is logged still isn't authorized. + client = Client() - def send_valid_request(client, counter_id): + def send_valid_request(counter_id): return client.post( reverse( - "counter:add_student_card", - kwargs={ - "customer_id": self.customer.customer.pk, - }, + "counter:add_student_card", kwargs={"customer_id": customer.pk} ), {"uid": "8B90734A802A8F"}, HTTP_REFERER=reverse( "counter:click", - kwargs={ - "counter_id": counter_id, - "user_id": self.customer.customer.pk, - }, + kwargs={"counter_id": counter_id, "user_id": customer.pk}, ), ) - assert send_valid_request(self.client, self.counter.id).status_code == 403 + # Send to a counter where you aren't logged in + assert send_valid_request(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 + client.force_login(self.club_admin) + assert send_valid_request(self.club_counter.id).status_code == 403 def test_delete_student_card_with_owner(self): self.client.force_login(self.customer) self.client.post( reverse( "counter:delete_student_card", - kwargs={ - "customer_id": self.customer.customer.pk, - "card_id": self.customer.customer.student_cards.first().id, - }, + kwargs={"customer_id": self.customer.customer.pk}, ) ) - assert not self.customer.customer.student_cards.exists() + self.customer.customer.refresh_from_db() + assert not hasattr(self.customer.customer, "student_card") - def test_delete_student_card_with_board_member(self): - self.client.force_login(self.board_admin) - self.client.post( - reverse( - "counter:delete_student_card", - kwargs={ - "customer_id": self.customer.customer.pk, - "card_id": self.customer.customer.student_cards.first().id, - }, + def test_delete_student_card_with_admin_user(self): + """Test that AE board members and root users can delete student cards""" + for user in self.board_admin, self.root: + self.client.force_login(user) + self.client.post( + reverse( + "counter:delete_student_card", + kwargs={"customer_id": self.customer.customer.pk}, + ) ) - ) - assert not self.customer.customer.student_cards.exists() - - def test_delete_student_card_with_root(self): - self.client.force_login(self.root) - self.client.post( - reverse( - "counter:delete_student_card", - kwargs={ - "customer_id": self.customer.customer.pk, - "card_id": self.customer.customer.student_cards.first().id, - }, - ) - ) - assert not self.customer.customer.student_cards.exists() + self.customer.customer.refresh_from_db() + assert not hasattr(self.customer.customer, "student_card") def test_delete_student_card_fail(self): + """Test that non-admin users cannot delete student cards""" self.client.force_login(self.subscriber) response = self.client.post( reverse( "counter:delete_student_card", - kwargs={ - "customer_id": self.customer.customer.pk, - "card_id": self.customer.customer.student_cards.first().id, - }, + kwargs={"customer_id": self.customer.customer.pk}, ) ) assert response.status_code == 403 - assert self.customer.customer.student_cards.exists() + self.subscriber.customer.refresh_from_db() + assert not hasattr(self.subscriber.customer, "student_card") def test_add_student_card_from_user_preferences(self): - # Test with owner of the card - self.client.force_login(self.customer) - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "8B90734A802A8F"}, - ) + users = [self.subscriber, self.board_admin, self.root] + uids = ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"] + for user, uid in itertools.product(users, uids): + self.customer.customer.student_card.delete() + self.client.force_login(user) + response = self.client.post( + reverse( + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, + ), + {"uid": uid}, + ) + assert response.status_code == 302 + response = self.client.get(response.url) - 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.board_admin) - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "8B90734A802A8A"}, - ) - - assert response.status_code == 302 - - response = self.client.get(response.url) - self.assertContains(response, text="8B90734A802A8A") - - # Test card with only numbers - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "04786547890123"}, - ) - assert response.status_code == 302 - - response = self.client.get(response.url) - self.assertContains(response, text="04786547890123") - - # Test card with only letters - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "ABCAAAFAAFAAAB"}, - ) - - 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) - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "8B90734A802A8B"}, - ) - - assert response.status_code == 302 - - response = self.client.get(response.url) - self.assertContains(response, text="8B90734A802A8B") + self.customer.customer.refresh_from_db() + assert self.customer.customer.student_card.uid == uid + self.assertContains(response, text="Enregistré") def test_add_student_card_from_user_preferences_fail(self): - self.client.force_login(self.customer) - # UID too short - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "8B90734A802A8"}, - ) - - self.assertContains(response, text="Cet UID est invalide") - - # UID too long - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "8B90734A802A8FA"}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with already existing card - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": self.valid_card.uid}, - ) - self.assertContains( - response, text="Un objet Student card avec ce champ Uid existe déjà." - ) - - # Test with lowercase - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "8b90734a802a9f"}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with white spaces - response = self.client.post( - reverse( - "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.subscriber) - response = self.client.post( - reverse( - "counter:add_student_card", - kwargs={"customer_id": self.customer.customer.pk}, - ), - {"uid": "8B90734A802A8F"}, - ) - assert response.status_code == 403 + customer = subscriber_user.make() + self.client.force_login(customer) + for uid, error_msg in self.invalid_uids(): + url = reverse( + "counter:add_student_card", kwargs={"customer_id": customer.customer.pk} + ) + response = self.client.post(url, {"uid": uid}) + self.assertContains(response, text="Cet UID est invalide") + self.assertContains(response, text=error_msg) + customer.refresh_from_db() + assert not hasattr(customer.customer, "student_card") class TestCustomerAccountId(TestCase): diff --git a/counter/urls.py b/counter/urls.py index e196894f..fa659ba0 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -81,7 +81,7 @@ urlpatterns = [ name="add_student_card", ), path( - "customer//card/delete//", + "customer//card/delete/", StudentCardDeleteView.as_view(), name="delete_student_card", ), diff --git a/counter/views/click.py b/counter/views/click.py index 2fa9684d..65e889ff 100644 --- a/counter/views/click.py +++ b/counter/views/click.py @@ -415,7 +415,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): kwargs["basket_total"] = self.sum_basket(self.request) kwargs["refill_form"] = self.refill_form or RefillForm() kwargs["barmens_can_refill"] = self.object.can_refill() - kwargs["student_card"] = StudentCardFormView.get_template_data( + kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( self.customer ).render(self.request) return kwargs diff --git a/counter/views/student_card.py b/counter/views/student_card.py index 99f67316..070e260d 100644 --- a/counter/views/student_card.py +++ b/counter/views/student_card.py @@ -15,9 +15,10 @@ from django.core.exceptions import PermissionDenied -from django.http import HttpRequest +from django.http import Http404, HttpRequest, HttpResponse from django.shortcuts import get_object_or_404 -from django.urls import reverse_lazy +from django.urls import reverse +from django.utils.translation import gettext as _ from django.views.generic.edit import DeleteView, FormView from core.utils import FormFragmentTemplateData @@ -32,16 +33,21 @@ class StudentCardDeleteView(DeleteView, CanEditMixin): model = StudentCard template_name = "core/delete_confirm.jinja" - pk_url_kwarg = "card_id" def dispatch(self, request, *args, **kwargs): self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) return super().dispatch(request, *args, **kwargs) + def get_object(self, queryset=None): + if not hasattr(self.customer, "student_card"): + raise Http404( + _("%(name)s has no registered student card") + % {"name": self.customer.user.get_full_name()} + ) + return self.customer.student_card + def get_success_url(self, **kwargs): - return reverse_lazy( - "core:user_prefs", kwargs={"user_id": self.customer.user.pk} - ) + return reverse("core:user_prefs", kwargs={"user_id": self.customer.user_id}) class StudentCardFormView(FormView): @@ -53,23 +59,22 @@ class StudentCardFormView(FormView): @classmethod def get_template_data( cls, customer: Customer - ) -> FormFragmentTemplateData[form_class]: + ) -> FormFragmentTemplateData[StudentCardForm]: """Get necessary data to pre-render the fragment""" - return FormFragmentTemplateData[cls.form_class]( + return FormFragmentTemplateData( form=cls.form_class(), template=cls.template_name, context={ - "action": reverse_lazy( + "action": reverse( "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"] + Customer.objects.select_related("student_card"), pk=kwargs["customer_id"] ) if not is_logged_in_counter(request) and not StudentCard.can_create( @@ -79,11 +84,12 @@ class StudentCardFormView(FormView): return super().dispatch(request, *args, **kwargs) - def form_valid(self, form): + def form_valid(self, form: StudentCardForm) -> HttpResponse: data = form.clean() - res = super(FormView, self).form_valid(form) - StudentCard(customer=self.customer, uid=data["uid"]).save() - return res + StudentCard.objects.update_or_create( + customer=self.customer, defaults={"uid": data["uid"]} + ) + return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index e122343d..1a2786bc 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-09 12:28+0100\n" +"POT-Creation-Date: 2024-12-11 09:34+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -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 -#: counter/templates/counter/fragments/create_student_card.jinja:21 +#: counter/templates/counter/fragments/create_student_card.jinja:18 #: counter/templates/counter/last_ops.jinja:35 #: counter/templates/counter/last_ops.jinja:65 #: election/templates/election/election_detail.jinja:191 @@ -950,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:193 +#: club/forms.py:149 counter/forms.py:189 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:196 +#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:192 #: election/views.py:170 subscription/forms.py:21 msgid "End date" msgstr "Date de fin" @@ -3048,7 +3048,7 @@ msgstr "Facture eboutic" msgid "Etickets" msgstr "Etickets" -#: core/templates/core/user_account.jinja:69 core/views/user.py:639 +#: core/templates/core/user_account.jinja:69 core/views/user.py:633 msgid "User has no account" msgstr "L'utilisateur n'a pas de compte" @@ -3371,7 +3371,7 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:48 counter/forms.py:166 +#: core/templates/core/user_tools.jinja:48 counter/forms.py:162 #: counter/views/mixins.py:89 msgid "Counters" msgstr "Comptoirs" @@ -3543,7 +3543,7 @@ msgstr "Parrain / Marraine" msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:310 counter/forms.py:80 trombi/views.py:151 +#: core/views/forms.py:310 counter/forms.py:78 trombi/views.py:151 msgid "Select user" msgstr "Choisir un utilisateur" @@ -3596,11 +3596,11 @@ msgstr "Galaxie" msgid "counter" msgstr "comptoir" -#: counter/forms.py:61 +#: counter/forms.py:59 msgid "This UID is invalid" msgstr "Cet UID est invalide" -#: counter/forms.py:109 +#: counter/forms.py:107 msgid "User not found" msgstr "Utilisateur non trouvé" @@ -3862,9 +3862,13 @@ msgstr "secret" msgid "uid" msgstr "uid" -#: counter/models.py:1144 +#: counter/models.py:1144 counter/models.py:1149 +msgid "student card" +msgstr "carte étudiante" + +#: counter/models.py:1150 msgid "student cards" -msgstr "cartes étudiante" +msgstr "cartes étudiantes" #: counter/templates/counter/activity.jinja:5 #: counter/templates/counter/activity.jinja:13 @@ -3929,7 +3933,7 @@ msgstr "Vente" #: counter/templates/counter/counter_click.jinja:50 #: counter/templates/counter/counter_click.jinja:115 -#: counter/templates/counter/fragments/create_student_card.jinja:10 +#: counter/templates/counter/fragments/create_student_card.jinja:11 #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 @@ -4032,17 +4036,17 @@ 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" +msgid "Student card" +msgstr "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/fragments/create_student_card.jinja:16 +msgid "Registered" +msgstr "Enregistré" + #: counter/templates/counter/invoices_call.jinja:8 #, python-format msgid "Invoices call for %(date)s" @@ -4313,6 +4317,11 @@ msgstr "Administration des comptoirs" msgid "Product types" msgstr "Types de produit" +#: counter/views/student_card.py:44 +#, python-format +msgid "%(name)s has no registered student card" +msgstr "%(name)s n'a pas de carte étudiante enregistrée" + #: eboutic/forms.py:88 msgid "The request was badly formatted." msgstr "La requête a été mal formatée."