mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-30 00:23:54 +00:00 
			
		
		
		
	Merge pull request #924 from ae-utbm/unique-student-card
Make student card unique per user
This commit is contained in:
		| @@ -69,7 +69,7 @@ class Command(BaseCommand): | |||||||
|             # sqlite doesn't support this operation |             # sqlite doesn't support this operation | ||||||
|             return |             return | ||||||
|         sqlcmd = StringIO() |         sqlcmd = StringIO() | ||||||
|         call_command("sqlsequencereset", *args, stdout=sqlcmd) |         call_command("sqlsequencereset", "--no-color", *args, stdout=sqlcmd) | ||||||
|         cursor = connection.cursor() |         cursor = connection.cursor() | ||||||
|         cursor.execute(sqlcmd.getvalue()) |         cursor.execute(sqlcmd.getvalue()) | ||||||
|  |  | ||||||
| @@ -137,11 +137,10 @@ class Command(BaseCommand): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         self.reset_index("club") |         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 = [ |         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="Eboutic", club=main_club, type="EBOUTIC"), | ||||||
|             Counter(name="AE", club=main_club, type="OFFICE"), |             Counter(name="AE", club=main_club, type="OFFICE"), | ||||||
|             Counter(name="Vidage comptes AE", club=main_club, type="OFFICE"), |             Counter(name="Vidage comptes AE", club=main_club, type="OFFICE"), | ||||||
|   | |||||||
| @@ -30,3 +30,8 @@ $shadow-color: rgb(223, 223, 223); | |||||||
| $background-button-color: hsl(0, 0%, 95%); | $background-button-color: hsl(0, 0%, 95%); | ||||||
|  |  | ||||||
| $deepblue: #354a5f; | $deepblue: #354a5f; | ||||||
|  |  | ||||||
|  | @mixin shadow { | ||||||
|  |   box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0, | ||||||
|  |               rgba(60, 64, 67, 0.15) 0 4px 8px 3px; | ||||||
|  | } | ||||||
| @@ -42,6 +42,32 @@ body { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | [tooltip] { | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [tooltip]::before { | ||||||
|  |   @include shadow; | ||||||
|  |   opacity: 0; | ||||||
|  |   z-index: 1; | ||||||
|  |   content: attr(tooltip); | ||||||
|  |   background: hsl(219.6, 20.8%, 96%); | ||||||
|  |   color: $black-color; | ||||||
|  |   border: 0.5px solid hsl(0, 0%, 50%); | ||||||
|  |   ; | ||||||
|  |   border-radius: 5px; | ||||||
|  |   padding: 5px; | ||||||
|  |   top: 1em; | ||||||
|  |   position: absolute; | ||||||
|  |   margin-top: 5px; | ||||||
|  |   white-space: nowrap; | ||||||
|  |   transition: opacity 500ms ease-out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [tooltip]:hover::before { | ||||||
|  |   opacity: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
| .ib { | .ib { | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   padding: 1px; |   padding: 1px; | ||||||
| @@ -79,8 +105,7 @@ body { | |||||||
| } | } | ||||||
|  |  | ||||||
| .shadow { | .shadow { | ||||||
|   box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0, |   @include shadow; | ||||||
|               rgba(60, 64, 67, 0.15) 0 4px 8px 3px; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .w_big { | .w_big { | ||||||
| @@ -308,6 +333,7 @@ body { | |||||||
|         font-size: 120%; |         font-size: 120%; | ||||||
|         background-color: unset; |         background-color: unset; | ||||||
|         position: relative; |         position: relative; | ||||||
|  |  | ||||||
|         &:after { |         &:after { | ||||||
|           content: ''; |           content: ''; | ||||||
|           position: absolute; |           position: absolute; | ||||||
| @@ -318,14 +344,17 @@ body { | |||||||
|           border-radius: 2px; |           border-radius: 2px; | ||||||
|           transition: all 0.2s ease-in-out; |           transition: all 0.2s ease-in-out; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         &:hover:after { |         &:hover:after { | ||||||
|           border-bottom-color: darken($primary-neutral-light-color, 20%); |           border-bottom-color: darken($primary-neutral-light-color, 20%); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         &.active:after { |         &.active:after { | ||||||
|           border-bottom-color: $primary-dark-color; |           border-bottom-color: $primary-dark-color; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     section { |     section { | ||||||
|       padding: 20px; |       padding: 20px; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -35,8 +35,9 @@ | |||||||
|     {% endif %} |     {% endif %} | ||||||
|  |  | ||||||
|  |  | ||||||
|     {% if student_card %} |     {% if student_card_fragment %} | ||||||
|       {{ student_card }} |       <h3>{% trans %}Student card{% endtrans %}</h3> | ||||||
|  |       {{ student_card_fragment }} | ||||||
|       <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 %} | ||||||
|   | |||||||
| @@ -559,10 +559,6 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): | |||||||
|     context_object_name = "profile" |     context_object_name = "profile" | ||||||
|     current_tab = "prefs" |     current_tab = "prefs" | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None): |  | ||||||
|         user = get_object_or_404(User, pk=self.kwargs["user_id"]) |  | ||||||
|         return user |  | ||||||
|  |  | ||||||
|     def get_form_kwargs(self): |     def get_form_kwargs(self): | ||||||
|         kwargs = super().get_form_kwargs() |         kwargs = super().get_form_kwargs() | ||||||
|         pref = self.object.preferences |         pref = self.object.preferences | ||||||
| @@ -572,12 +568,10 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): | |||||||
|     def get_context_data(self, **kwargs): |     def get_context_data(self, **kwargs): | ||||||
|         kwargs = super().get_context_data(**kwargs) |         kwargs = super().get_context_data(**kwargs) | ||||||
|  |  | ||||||
|         if not ( |         if not hasattr(self.object, "trombi_user"): | ||||||
|             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"] = StudentCardFormView.get_template_data( |             kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( | ||||||
|                 self.object.customer |                 self.object.customer | ||||||
|             ).render(self.request) |             ).render(self.request) | ||||||
|         return kwargs |         return kwargs | ||||||
|   | |||||||
| @@ -50,9 +50,7 @@ class StudentCardForm(forms.ModelForm): | |||||||
|     class Meta: |     class Meta: | ||||||
|         model = StudentCard |         model = StudentCard | ||||||
|         fields = ["uid"] |         fields = ["uid"] | ||||||
|         widgets = { |         widgets = {"uid": NFCTextInput} | ||||||
|             "uid": NFCTextInput, |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     def clean(self): |     def clean(self): | ||||||
|         cleaned_data = super().clean() |         cleaned_data = super().clean() | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								counter/migrations/0026_alter_studentcard_customer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								counter/migrations/0026_alter_studentcard_customer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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", | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -1138,20 +1138,22 @@ class StudentCard(models.Model): | |||||||
|     uid = models.CharField( |     uid = models.CharField( | ||||||
|         _("uid"), max_length=UID_SIZE, unique=True, validators=[MinLengthValidator(4)] |         _("uid"), max_length=UID_SIZE, unique=True, validators=[MinLengthValidator(4)] | ||||||
|     ) |     ) | ||||||
|     customer = models.ForeignKey( |     customer = models.OneToOneField( | ||||||
|         Customer, |         Customer, | ||||||
|         related_name="student_cards", |         related_name="student_card", | ||||||
|         verbose_name=_("student cards"), |         verbose_name=_("student card"), | ||||||
|         null=False, |  | ||||||
|         blank=False, |  | ||||||
|         on_delete=models.CASCADE, |         on_delete=models.CASCADE, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _("student card") | ||||||
|  |         verbose_name_plural = _("student cards") | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.uid |         return self.uid | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def is_valid(uid): |     def is_valid(uid: str) -> bool: | ||||||
|         return ( |         return ( | ||||||
|             (uid.isupper() or uid.isnumeric()) |             (uid.isupper() or uid.isnumeric()) | ||||||
|             and len(uid) == StudentCard.UID_SIZE |             and len(uid) == StudentCard.UID_SIZE | ||||||
|   | |||||||
| @@ -31,7 +31,8 @@ | |||||||
|       <p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p> |       <p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p> | ||||||
|  |  | ||||||
|       {% if counter.type == 'BAR' %} |       {% if counter.type == 'BAR' %} | ||||||
|         {{ student_card }} |         <h5>{% trans %}Student card{% endtrans %}</h3> | ||||||
|  |         {{ student_card_fragment }} | ||||||
|       {% endif %} |       {% endif %} | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <div id="student_card_form"> | <div id="student_card_form"> | ||||||
|   <h3>{% trans %}Add a student card{% endtrans %}</h3> |   {% if not customer.student_card %} | ||||||
|     <form |     <form | ||||||
|       hx-post="{{ action }}" |       hx-post="{{ action }}" | ||||||
|       hx-swap="outerHTML" |       hx-swap="outerHTML" | ||||||
| @@ -8,22 +8,22 @@ | |||||||
|       {% csrf_token %} |       {% csrf_token %} | ||||||
|       {{ form.as_p() }} |       {{ form.as_p() }} | ||||||
|       <input type="submit" value="{% trans %}Go{% endtrans %}"/> |       <input type="submit" value="{% trans %}Go{% endtrans %}"/> | ||||||
|  |  | ||||||
|     </form> |     </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> |     <em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em> | ||||||
|  |   {% else %} | ||||||
|  |     <p> | ||||||
|  |       <span tooltip="{% trans uid=customer.student_card.uid %}uid: {{ uid }} {% endtrans %}"> | ||||||
|  |         {% trans %}Card registered{% endtrans %} | ||||||
|  |         <i class="fa fa-check" style="color: green"></i> | ||||||
|  |       </span> | ||||||
|  |         -   | ||||||
|  |       <button | ||||||
|  |         hx-get="{{ url('counter:delete_student_card', customer_id=customer.pk) }}" | ||||||
|  |         hx-swap="outerHTML" | ||||||
|  |         hx-target="#student_card_form" | ||||||
|  |       > | ||||||
|  |         {% trans %}Delete{% endtrans %} | ||||||
|  |       </button> | ||||||
|  |     </p> | ||||||
|   {% endif %} |   {% endif %} | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | <div id="student_card_form"> | ||||||
|  |   <form hx-post="{{ action }}" hx-swap="outerHTML" hx-target="#student_card_form"> | ||||||
|  |     {% csrf_token %} | ||||||
|  |     <p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p> | ||||||
|  |     <input type="submit" value="{% trans %}Confirm{% endtrans %}" /> | ||||||
|  |     <input | ||||||
|  |       hx-get="{{ action_cancel }}" | ||||||
|  |       hx-swap="outerHTML" | ||||||
|  |       hx-target="#student_card_form" | ||||||
|  |       type="submit" | ||||||
|  |       name="cancel" | ||||||
|  |       value="{% trans %}Cancel{% endtrans %}" | ||||||
|  |     /> | ||||||
|  |   </form> | ||||||
|  | </div> | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import itertools | ||||||
| import json | import json | ||||||
| import string | import string | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
| @@ -175,7 +176,6 @@ class TestStudentCard(TestCase): | |||||||
|     @classmethod |     @classmethod | ||||||
|     def setUpTestData(cls): |     def setUpTestData(cls): | ||||||
|         cls.customer = subscriber_user.make() |         cls.customer = subscriber_user.make() | ||||||
|         cls.customer.save() |  | ||||||
|         cls.barmen = subscriber_user.make(password=make_password("plop")) |         cls.barmen = subscriber_user.make(password=make_password("plop")) | ||||||
|         cls.board_admin = board_user.make() |         cls.board_admin = board_user.make() | ||||||
|         cls.club_admin = baker.make(User) |         cls.club_admin = baker.make(User) | ||||||
| @@ -198,14 +198,30 @@ class TestStudentCard(TestCase): | |||||||
|             StudentCard, customer=cls.customer.customer, uid="8A89B82018B0A0" |             StudentCard, customer=cls.customer.customer, uid="8A89B82018B0A0" | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def setUp(self): |     def login_in_counter(self): | ||||||
|         # 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": self.barmen.username, "password": "plop"}, |             {"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): |     def test_search_user_with_student_card(self): | ||||||
|  |         self.login_in_counter() | ||||||
|         response = self.client.post( |         response = self.client.post( | ||||||
|             reverse("counter:details", args=[self.counter.id]), |             reverse("counter:details", args=[self.counter.id]), | ||||||
|             {"code": self.valid_card.uid}, |             {"code": self.valid_card.uid}, | ||||||
| @@ -213,396 +229,167 @@ class TestStudentCard(TestCase): | |||||||
|  |  | ||||||
|         assert response.url == reverse( |         assert response.url == reverse( | ||||||
|             "counter:click", |             "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): |     def test_add_student_card_from_counter(self): | ||||||
|         # Test card with mixed letters and numbers |         self.login_in_counter() | ||||||
|  |         for uid in ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"]: | ||||||
|  |             customer = subscriber_user.make().customer | ||||||
|             response = self.client.post( |             response = self.client.post( | ||||||
|                 reverse( |                 reverse( | ||||||
|                 "counter:add_student_card", |                     "counter:add_student_card", kwargs={"customer_id": customer.pk} | ||||||
|                 kwargs={ |  | ||||||
|                     "customer_id": self.customer.customer.pk, |  | ||||||
|                 }, |  | ||||||
|                 ), |                 ), | ||||||
|             {"uid": "8B90734A802A8F"}, |                 {"uid": uid}, | ||||||
|                 HTTP_REFERER=reverse( |                 HTTP_REFERER=reverse( | ||||||
|                     "counter:click", |                     "counter:click", | ||||||
|                 kwargs={ |                     kwargs={"counter_id": self.counter.id, "user_id": customer.pk}, | ||||||
|                     "counter_id": self.counter.id, |  | ||||||
|                     "user_id": self.customer.customer.pk, |  | ||||||
|                 }, |  | ||||||
|                 ), |                 ), | ||||||
|             ) |             ) | ||||||
|             assert response.status_code == 302 |             assert response.status_code == 302 | ||||||
|         self.assertContains(self.client.get(response.url), text="8B90734A802A8F") |             customer.refresh_from_db() | ||||||
|  |             assert hasattr(customer, "student_card") | ||||||
|         # Test card with only numbers |             assert customer.student_card.uid == uid | ||||||
|         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") |  | ||||||
|  |  | ||||||
|     def test_add_student_card_from_counter_fail(self): |     def test_add_student_card_from_counter_fail(self): | ||||||
|         # UID too short |         self.login_in_counter() | ||||||
|  |         customer = subscriber_user.make().customer | ||||||
|  |         for uid, error_msg in self.invalid_uids(): | ||||||
|             response = self.client.post( |             response = self.client.post( | ||||||
|                 reverse( |                 reverse( | ||||||
|                 "counter:add_student_card", |                     "counter:add_student_card", kwargs={"customer_id": customer.pk} | ||||||
|                 kwargs={ |  | ||||||
|                     "customer_id": self.customer.customer.pk, |  | ||||||
|                 }, |  | ||||||
|                 ), |                 ), | ||||||
|             {"uid": "8B90734A802A8"}, |                 {"uid": uid}, | ||||||
|                 HTTP_REFERER=reverse( |                 HTTP_REFERER=reverse( | ||||||
|                     "counter:click", |                     "counter:click", | ||||||
|                 kwargs={ |                     kwargs={"counter_id": self.counter.id, "user_id": customer.pk}, | ||||||
|                     "counter_id": self.counter.id, |  | ||||||
|                     "user_id": self.customer.customer.pk, |  | ||||||
|                 }, |  | ||||||
|                 ), |                 ), | ||||||
|             ) |             ) | ||||||
|             self.assertContains(response, text="Cet UID est invalide") |             self.assertContains(response, text="Cet UID est invalide") | ||||||
|  |             self.assertContains(response, text=error_msg) | ||||||
|         # UID too long |             customer.refresh_from_db() | ||||||
|         response = self.client.post( |             assert not hasattr(customer, "student_card") | ||||||
|             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.") |  | ||||||
|  |  | ||||||
|     def test_add_student_card_from_counter_unauthorized(self): |     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): |         def send_valid_request(client, counter_id): | ||||||
|             return client.post( |             return client.post( | ||||||
|                 reverse( |                 reverse( | ||||||
|                     "counter:add_student_card", |                     "counter:add_student_card", kwargs={"customer_id": self.customer.pk} | ||||||
|                     kwargs={ |  | ||||||
|                         "customer_id": self.customer.customer.pk, |  | ||||||
|                     }, |  | ||||||
|                 ), |                 ), | ||||||
|                 {"uid": "8B90734A802A8F"}, |                 {"uid": "8B90734A802A8F"}, | ||||||
|                 HTTP_REFERER=reverse( |                 HTTP_REFERER=reverse( | ||||||
|                     "counter:click", |                     "counter:click", | ||||||
|                     kwargs={ |                     kwargs={"counter_id": counter_id, "user_id": self.customer.pk}, | ||||||
|                         "counter_id": counter_id, |  | ||||||
|                         "user_id": self.customer.customer.pk, |  | ||||||
|                     }, |  | ||||||
|                 ), |                 ), | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |         # Send to a counter where you aren't logged in | ||||||
|         assert send_valid_request(self.client, self.counter.id).status_code == 403 |         assert send_valid_request(self.client, self.counter.id).status_code == 403 | ||||||
|  |  | ||||||
|  |         self.login_in_counter() | ||||||
|  |         barman = subscriber_user.make() | ||||||
|  |         self.counter.sellers.add(barman) | ||||||
|  |         # We want to test sending requests from another counter while | ||||||
|  |         # we are currently registered to another counter | ||||||
|  |         # so we connect to a counter and | ||||||
|  |         # 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() | ||||||
|  |         # Send to a counter where you aren't logged in | ||||||
|  |         assert send_valid_request(client, self.counter.id).status_code == 403 | ||||||
|  |  | ||||||
|         # Send to a non bar counter |         # Send to a non bar counter | ||||||
|         self.client.force_login(self.club_admin) |         client.force_login(self.club_admin) | ||||||
|         assert send_valid_request(self.client, self.club_counter.id).status_code == 403 |         assert send_valid_request(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.customer) |         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.customer.customer.pk}, | ||||||
|                     "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_with_board_member(self): |     def test_delete_student_card_with_admin_user(self): | ||||||
|         self.client.force_login(self.board_admin) |         """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( |             self.client.post( | ||||||
|                 reverse( |                 reverse( | ||||||
|                     "counter:delete_student_card", |                     "counter:delete_student_card", | ||||||
|                 kwargs={ |                     kwargs={"customer_id": self.customer.customer.pk}, | ||||||
|                     "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_with_root(self): |     def test_delete_student_card_from_counter(self): | ||||||
|         self.client.force_login(self.root) |         self.login_in_counter() | ||||||
|         self.client.post( |         self.client.post( | ||||||
|             reverse( |             reverse( | ||||||
|                 "counter:delete_student_card", |                 "counter:delete_student_card", | ||||||
|  |                 kwargs={"customer_id": self.customer.customer.pk}, | ||||||
|  |             ), | ||||||
|  |             http_referer=reverse( | ||||||
|  |                 "counter:click", | ||||||
|                 kwargs={ |                 kwargs={ | ||||||
|                     "customer_id": self.customer.customer.pk, |                     "counter_id": self.counter.id, | ||||||
|                     "card_id": self.customer.customer.student_cards.first().id, |                     "user_id": self.customer.customer.pk, | ||||||
|                 }, |                 }, | ||||||
|  |             ), | ||||||
|         ) |         ) | ||||||
|         ) |         self.customer.customer.refresh_from_db() | ||||||
|         assert not self.customer.customer.student_cards.exists() |         assert not hasattr(self.customer.customer, "student_card") | ||||||
|  |  | ||||||
|     def test_delete_student_card_fail(self): |     def test_delete_student_card_fail(self): | ||||||
|  |         """Test that non-admin users cannot delete student cards""" | ||||||
|         self.client.force_login(self.subscriber) |         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.customer.customer.pk}, | ||||||
|                     "customer_id": self.customer.customer.pk, |  | ||||||
|                     "card_id": self.customer.customer.student_cards.first().id, |  | ||||||
|                 }, |  | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|         assert response.status_code == 403 |         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): |     def test_add_student_card_from_user_preferences(self): | ||||||
|         # Test with owner of the card |         users = [self.customer, self.board_admin, self.root] | ||||||
|         self.client.force_login(self.customer) |         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( |             response = self.client.post( | ||||||
|                 reverse( |                 reverse( | ||||||
|                     "counter:add_student_card", |                     "counter:add_student_card", | ||||||
|                     kwargs={"customer_id": self.customer.customer.pk}, |                     kwargs={"customer_id": self.customer.customer.pk}, | ||||||
|                 ), |                 ), | ||||||
|             {"uid": "8B90734A802A8F"}, |                 {"uid": uid}, | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         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 |             assert response.status_code == 302 | ||||||
|  |  | ||||||
|             response = self.client.get(response.url) |             response = self.client.get(response.url) | ||||||
|         self.assertContains(response, text="04786547890123") |  | ||||||
|  |  | ||||||
|         # Test card with only letters |             self.customer.customer.refresh_from_db() | ||||||
|         response = self.client.post( |             assert self.customer.customer.student_card.uid == uid | ||||||
|             reverse( |             self.assertContains(response, text="Carte enregistrée") | ||||||
|                 "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") |  | ||||||
|  |  | ||||||
|     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.customer) |         customer = subscriber_user.make() | ||||||
|         # UID too short |         self.client.force_login(customer) | ||||||
|         response = self.client.post( |         for uid, error_msg in self.invalid_uids(): | ||||||
|             reverse( |             url = reverse( | ||||||
|                 "counter:add_student_card", |                 "counter:add_student_card", kwargs={"customer_id": customer.customer.pk} | ||||||
|                 kwargs={"customer_id": self.customer.customer.pk}, |  | ||||||
|             ), |  | ||||||
|             {"uid": "8B90734A802A8"}, |  | ||||||
|             ) |             ) | ||||||
|  |             response = self.client.post(url, {"uid": uid}) | ||||||
|             self.assertContains(response, text="Cet UID est invalide") |             self.assertContains(response, text="Cet UID est invalide") | ||||||
|  |             self.assertContains(response, text=error_msg) | ||||||
|         # UID too long |             customer.refresh_from_db() | ||||||
|         response = self.client.post( |             assert not hasattr(customer.customer, "student_card") | ||||||
|             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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestCustomerAccountId(TestCase): | class TestCustomerAccountId(TestCase): | ||||||
|   | |||||||
| @@ -81,7 +81,7 @@ urlpatterns = [ | |||||||
|         name="add_student_card", |         name="add_student_card", | ||||||
|     ), |     ), | ||||||
|     path( |     path( | ||||||
|         "customer/<int:customer_id>/card/delete/<int:card_id>/", |         "customer/<int:customer_id>/card/delete/", | ||||||
|         StudentCardDeleteView.as_view(), |         StudentCardDeleteView.as_view(), | ||||||
|         name="delete_student_card", |         name="delete_student_card", | ||||||
|     ), |     ), | ||||||
|   | |||||||
| @@ -415,7 +415,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): | |||||||
|         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["barmens_can_refill"] = self.object.can_refill() |         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 |             self.customer | ||||||
|         ).render(self.request) |         ).render(self.request) | ||||||
|         return kwargs |         return kwargs | ||||||
|   | |||||||
| @@ -15,32 +15,50 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
| from django.core.exceptions import PermissionDenied | 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.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 django.views.generic.edit import DeleteView, FormView | ||||||
|  |  | ||||||
| from core.utils import FormFragmentTemplateData | from core.utils import FormFragmentTemplateData | ||||||
| from core.views import CanEditMixin | from core.views import can_edit | ||||||
| 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 | from counter.utils import is_logged_in_counter | ||||||
|  |  | ||||||
|  |  | ||||||
| class StudentCardDeleteView(DeleteView, CanEditMixin): | class StudentCardDeleteView(DeleteView): | ||||||
|     """View used to delete a card from a user.""" |     """View used to delete a card from a user. This is a fragment view !""" | ||||||
|  |  | ||||||
|     model = StudentCard |     model = StudentCard | ||||||
|     template_name = "core/delete_confirm.jinja" |     template_name = "counter/fragments/delete_student_card.jinja" | ||||||
|     pk_url_kwarg = "card_id" |  | ||||||
|  |  | ||||||
|     def dispatch(self, request, *args, **kwargs): |     def dispatch(self, request: HttpRequest, *args, **kwargs): | ||||||
|         self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) |         self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) | ||||||
|  |         if not is_logged_in_counter(request) and not can_edit( | ||||||
|  |             self.get_object(), request.user | ||||||
|  |         ): | ||||||
|  |             raise PermissionDenied() | ||||||
|         return super().dispatch(request, *args, **kwargs) |         return super().dispatch(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs): | ||||||
|  |         context = super().get_context_data(**kwargs) | ||||||
|  |         context["action"] = self.request.path | ||||||
|  |         context["action_cancel"] = self.get_success_url() | ||||||
|  |         return context | ||||||
|  |  | ||||||
|  |     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): |     def get_success_url(self, **kwargs): | ||||||
|         return reverse_lazy( |         return reverse( | ||||||
|             "core:user_prefs", kwargs={"user_id": self.customer.user.pk} |             "counter:add_student_card", kwargs={"customer_id": self.customer.pk} | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -53,23 +71,22 @@ class StudentCardFormView(FormView): | |||||||
|     @classmethod |     @classmethod | ||||||
|     def get_template_data( |     def get_template_data( | ||||||
|         cls, customer: Customer |         cls, customer: Customer | ||||||
|     ) -> FormFragmentTemplateData[form_class]: |     ) -> FormFragmentTemplateData[StudentCardForm]: | ||||||
|         """Get necessary data to pre-render the fragment""" |         """Get necessary data to pre-render the fragment""" | ||||||
|         return FormFragmentTemplateData[cls.form_class]( |         return FormFragmentTemplateData( | ||||||
|             form=cls.form_class(), |             form=cls.form_class(), | ||||||
|             template=cls.template_name, |             template=cls.template_name, | ||||||
|             context={ |             context={ | ||||||
|                 "action": reverse_lazy( |                 "action": reverse( | ||||||
|                     "counter:add_student_card", kwargs={"customer_id": customer.pk} |                     "counter:add_student_card", kwargs={"customer_id": customer.pk} | ||||||
|                 ), |                 ), | ||||||
|                 "customer": customer, |                 "customer": customer, | ||||||
|                 "student_cards": customer.student_cards.all(), |  | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def dispatch(self, request: HttpRequest, *args, **kwargs): |     def dispatch(self, request: HttpRequest, *args, **kwargs): | ||||||
|         self.customer = get_object_or_404( |         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( |         if not is_logged_in_counter(request) and not StudentCard.can_create( | ||||||
| @@ -79,11 +96,12 @@ class StudentCardFormView(FormView): | |||||||
|  |  | ||||||
|         return super().dispatch(request, *args, **kwargs) |         return super().dispatch(request, *args, **kwargs) | ||||||
|  |  | ||||||
|     def form_valid(self, form): |     def form_valid(self, form: StudentCardForm) -> HttpResponse: | ||||||
|         data = form.clean() |         data = form.clean() | ||||||
|         res = super(FormView, self).form_valid(form) |         StudentCard.objects.update_or_create( | ||||||
|         StudentCard(customer=self.customer, uid=data["uid"]).save() |             customer=self.customer, defaults={"uid": data["uid"]} | ||||||
|         return res |         ) | ||||||
|  |         return super().form_valid(form) | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs): |     def get_context_data(self, **kwargs): | ||||||
|         context = super().get_context_data(**kwargs) |         context = super().get_context_data(**kwargs) | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
| msgid "" | msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Report-Msgid-Bugs-To: \n" | "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" | "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" | ||||||
| @@ -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 | ||||||
| #: counter/templates/counter/fragments/create_student_card.jinja:21 | #: counter/templates/counter/fragments/create_student_card.jinja:22 | ||||||
| #: 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 | ||||||
| @@ -950,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:193 | #: club/forms.py:149 counter/forms.py:189 | ||||||
| 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: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 | #: election/views.py:170 subscription/forms.py:21 | ||||||
| msgid "End date" | msgid "End date" | ||||||
| msgstr "Date de fin" | msgstr "Date de fin" | ||||||
| @@ -2574,18 +2574,21 @@ msgstr "Confirmation de suppression" | |||||||
|  |  | ||||||
| #: core/templates/core/delete_confirm.jinja:16 | #: core/templates/core/delete_confirm.jinja:16 | ||||||
| #: core/templates/core/file_delete_confirm.jinja:29 | #: core/templates/core/file_delete_confirm.jinja:29 | ||||||
|  | #: counter/templates/counter/fragments/delete_student_card.jinja:4 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "Are you sure you want to delete \"%(obj)s\"?" | msgid "Are you sure you want to delete \"%(obj)s\"?" | ||||||
| msgstr "Êtes-vous sûr de vouloir supprimer \"%(obj)s\" ?" | msgstr "Êtes-vous sûr de vouloir supprimer \"%(obj)s\" ?" | ||||||
|  |  | ||||||
| #: core/templates/core/delete_confirm.jinja:17 | #: core/templates/core/delete_confirm.jinja:17 | ||||||
| #: core/templates/core/file_delete_confirm.jinja:36 | #: core/templates/core/file_delete_confirm.jinja:36 | ||||||
|  | #: counter/templates/counter/fragments/delete_student_card.jinja:5 | ||||||
| msgid "Confirm" | msgid "Confirm" | ||||||
| msgstr "Confirmation" | 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:104 | #: counter/templates/counter/counter_click.jinja:104 | ||||||
|  | #: counter/templates/counter/fragments/delete_student_card.jinja:12 | ||||||
| #: sas/templates/sas/ask_picture_removal.jinja:20 | #: sas/templates/sas/ask_picture_removal.jinja:20 | ||||||
| msgid "Cancel" | msgid "Cancel" | ||||||
| msgstr "Annuler" | msgstr "Annuler" | ||||||
| @@ -3048,7 +3051,7 @@ msgstr "Facture eboutic" | |||||||
| msgid "Etickets" | msgid "Etickets" | ||||||
| msgstr "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" | msgid "User has no account" | ||||||
| msgstr "L'utilisateur n'a pas de compte" | msgstr "L'utilisateur n'a pas de compte" | ||||||
|  |  | ||||||
| @@ -3371,7 +3374,7 @@ 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:166 | #: core/templates/core/user_tools.jinja:48 counter/forms.py:162 | ||||||
| #: counter/views/mixins.py:89 | #: counter/views/mixins.py:89 | ||||||
| msgid "Counters" | msgid "Counters" | ||||||
| msgstr "Comptoirs" | msgstr "Comptoirs" | ||||||
| @@ -3543,7 +3546,7 @@ msgstr "Parrain / Marraine" | |||||||
| msgid "Godchild" | msgid "Godchild" | ||||||
| msgstr "Fillot / Fillote" | 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" | msgid "Select user" | ||||||
| msgstr "Choisir un utilisateur" | msgstr "Choisir un utilisateur" | ||||||
|  |  | ||||||
| @@ -3596,11 +3599,11 @@ msgstr "Galaxie" | |||||||
| msgid "counter" | msgid "counter" | ||||||
| msgstr "comptoir" | msgstr "comptoir" | ||||||
|  |  | ||||||
| #: counter/forms.py:61 | #: counter/forms.py:59 | ||||||
| msgid "This UID is invalid" | msgid "This UID is invalid" | ||||||
| msgstr "Cet UID est invalide" | msgstr "Cet UID est invalide" | ||||||
|  |  | ||||||
| #: counter/forms.py:109 | #: counter/forms.py:107 | ||||||
| msgid "User not found" | msgid "User not found" | ||||||
| msgstr "Utilisateur non trouvé" | msgstr "Utilisateur non trouvé" | ||||||
|  |  | ||||||
| @@ -3862,9 +3865,13 @@ msgstr "secret" | |||||||
| msgid "uid" | msgid "uid" | ||||||
| msgstr "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" | msgid "student cards" | ||||||
| msgstr "cartes étudiante" | msgstr "cartes étudiantes" | ||||||
|  |  | ||||||
| #: counter/templates/counter/activity.jinja:5 | #: counter/templates/counter/activity.jinja:5 | ||||||
| #: counter/templates/counter/activity.jinja:13 | #: counter/templates/counter/activity.jinja:13 | ||||||
| @@ -3929,7 +3936,7 @@ msgstr "Vente" | |||||||
|  |  | ||||||
| #: counter/templates/counter/counter_click.jinja:50 | #: counter/templates/counter/counter_click.jinja:50 | ||||||
| #: counter/templates/counter/counter_click.jinja:115 | #: 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 | #: 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 | ||||||
| @@ -4032,17 +4039,22 @@ 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 | #: counter/templates/counter/fragments/create_student_card.jinja:2 | ||||||
| msgid "Add a student card" | msgid "Student card" | ||||||
| msgstr "Ajouter une carte étudiante" | msgstr "Carte étudiante" | ||||||
|  |  | ||||||
| #: counter/templates/counter/fragments/create_student_card.jinja:13 | #: 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." | msgid "No student card registered." | ||||||
| msgstr "Aucune carte étudiante enregistrée." | msgstr "Aucune carte étudiante enregistrée." | ||||||
|  |  | ||||||
|  | #: counter/templates/counter/fragments/create_student_card.jinja:16 | ||||||
|  | msgid "Card registered" | ||||||
|  | msgstr "Carte enregistrée" | ||||||
|  |  | ||||||
|  | #: counter/templates/counter/fragments/create_student_card.jinja:17 | ||||||
|  | #, python-format | ||||||
|  | msgid "uid: %(uid)s " | ||||||
|  | msgstr "uid: %(uid)s" | ||||||
|  |  | ||||||
| #: 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" | ||||||
| @@ -4313,6 +4325,11 @@ msgstr "Administration des comptoirs" | |||||||
| msgid "Product types" | msgid "Product types" | ||||||
| msgstr "Types de produit" | msgstr "Types de produit" | ||||||
|  |  | ||||||
|  | #: counter/views/student_card.py:54 | ||||||
|  | #, 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 | #: 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." | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user