mirror of
https://github.com/ae-utbm/sith.git
synced 2024-12-22 07:41:14 +00:00
feat: make student card unique per user
This commit is contained in:
parent
3b7e338808
commit
466fe58763
@ -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"),
|
||||
|
@ -35,8 +35,8 @@
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if student_card %}
|
||||
{{ student_card }}
|
||||
{% if student_card_fragment %}
|
||||
{{ student_card_fragment }}
|
||||
<p class="justify">
|
||||
{% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually
|
||||
add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
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"), 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
|
||||
|
@ -31,7 +31,7 @@
|
||||
<p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p>
|
||||
|
||||
{% if counter.type == 'BAR' %}
|
||||
{{ student_card }}
|
||||
{{ student_card_fragment }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div id="student_card_form">
|
||||
<h3>{% trans %}Add a student card{% endtrans %}</h3>
|
||||
<h3>{% trans %}Student card{% endtrans %}</h3>
|
||||
{% if not customer.student_card %}
|
||||
<form
|
||||
hx-post="{{ action }}"
|
||||
hx-swap="outerHTML"
|
||||
@ -8,22 +9,14 @@
|
||||
{% csrf_token %}
|
||||
{{ form.as_p() }}
|
||||
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
|
||||
|
||||
</form>
|
||||
<h6>{% trans %}Registered cards{% endtrans %}</h6>
|
||||
{% if student_cards %}
|
||||
|
||||
<ul>
|
||||
{% for card in student_cards %}
|
||||
<li>
|
||||
{{ card.uid }}
|
||||
<a href="{{ url('counter:delete_student_card', customer_id=customer.pk, card_id=card.id) }}">
|
||||
<em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em>
|
||||
{% else %}
|
||||
<p>
|
||||
{% trans %}Registered{% endtrans %} <i class="fa fa-check"></i> -
|
||||
<a href="{{ url('counter:delete_student_card', customer_id=customer.pk) }}">
|
||||
{% trans %}Delete{% endtrans %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -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
|
||||
for uid in ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"]:
|
||||
customer = subscriber_user.make().customer
|
||||
response = self.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"},
|
||||
{"uid": uid},
|
||||
HTTP_REFERER=reverse(
|
||||
"counter:click",
|
||||
kwargs={
|
||||
"counter_id": self.counter.id,
|
||||
"user_id": self.customer.customer.pk,
|
||||
},
|
||||
kwargs={"counter_id": self.counter.id, "user_id": 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")
|
||||
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
|
||||
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": self.customer.customer.pk,
|
||||
},
|
||||
"counter:add_student_card", kwargs={"customer_id": customer.pk}
|
||||
),
|
||||
{"uid": "8B90734A802A8"},
|
||||
{"uid": uid},
|
||||
HTTP_REFERER=reverse(
|
||||
"counter:click",
|
||||
kwargs={
|
||||
"counter_id": self.counter.id,
|
||||
"user_id": self.customer.customer.pk,
|
||||
},
|
||||
kwargs={"counter_id": self.counter.id, "user_id": 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.")
|
||||
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)
|
||||
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,
|
||||
"card_id": self.customer.customer.student_cards.first().id,
|
||||
},
|
||||
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)
|
||||
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": "8B90734A802A8F"},
|
||||
)
|
||||
|
||||
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"},
|
||||
{"uid": uid},
|
||||
)
|
||||
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"},
|
||||
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")
|
||||
|
||||
# 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
|
||||
self.assertContains(response, text=error_msg)
|
||||
customer.refresh_from_db()
|
||||
assert not hasattr(customer.customer, "student_card")
|
||||
|
||||
|
||||
class TestCustomerAccountId(TestCase):
|
||||
|
@ -81,7 +81,7 @@ urlpatterns = [
|
||||
name="add_student_card",
|
||||
),
|
||||
path(
|
||||
"customer/<int:customer_id>/card/delete/<int:card_id>/",
|
||||
"customer/<int:customer_id>/card/delete/",
|
||||
StudentCardDeleteView.as_view(),
|
||||
name="delete_student_card",
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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_success_url(self, **kwargs):
|
||||
return reverse_lazy(
|
||||
"core:user_prefs", kwargs={"user_id": self.customer.user.pk}
|
||||
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("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)
|
||||
|
@ -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 <thomas.girod@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: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."
|
||||
|
Loading…
Reference in New Issue
Block a user