diff --git a/core/static/bundled/utils/web-components.ts b/core/static/bundled/utils/web-components.ts
index 8bec98f9..c2b089c5 100644
--- a/core/static/bundled/utils/web-components.ts
+++ b/core/static/bundled/utils/web-components.ts
@@ -6,7 +6,16 @@
**/
export function registerComponent(name: string, options?: ElementDefinitionOptions) {
return (component: CustomElementConstructor) => {
- window.customElements.define(name, component, options);
+ try {
+ window.customElements.define(name, component, options);
+ } catch (e) {
+ if (e instanceof DOMException) {
+ // biome-ignore lint/suspicious/noConsole: it's handy to troobleshot
+ console.warn(e.message);
+ return;
+ }
+ throw e;
+ }
};
}
diff --git a/counter/forms.py b/counter/forms.py
index 84a92512..91e7c3dc 100644
--- a/counter/forms.py
+++ b/counter/forms.py
@@ -45,9 +45,7 @@ class BillingInfoForm(forms.ModelForm):
class StudentCardForm(forms.ModelForm):
- """Form for adding student cards
- Only used for user profile since CounterClick is to complicated.
- """
+ """Form for adding student cards"""
class Meta:
model = StudentCard
@@ -114,14 +112,6 @@ class GetUserForm(forms.Form):
return cleaned_data
-class NFCCardForm(forms.Form):
- student_card_uid = forms.CharField(
- max_length=StudentCard.UID_SIZE,
- required=False,
- widget=NFCTextInput,
- )
-
-
class RefillForm(forms.ModelForm):
error_css_class = "error"
required_css_class = "required"
diff --git a/counter/templates/counter/add_student_card_fragment.jinja b/counter/templates/counter/add_student_card_fragment.jinja
new file mode 100644
index 00000000..6446ae01
--- /dev/null
+++ b/counter/templates/counter/add_student_card_fragment.jinja
@@ -0,0 +1,26 @@
+
diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja
index cb6bb9cf..e72eb77e 100644
--- a/counter/templates/counter/counter_click.jinja
+++ b/counter/templates/counter/counter_click.jinja
@@ -29,27 +29,12 @@
{{ user_mini_profile(customer.user) }}
{{ user_subscription(customer.user) }}
{% trans %}Amount: {% endtrans %}{{ customer.amount }} €
-
- {% trans %}Registered cards{% endtrans %}
- {% if student_cards %}
-
- {% for card in student_cards %}
- - {{ card.uid }}
- {% endfor %}
-
- {% else %}
- {% trans %}No card registered{% endtrans %}
- {% endif %}
+
diff --git a/counter/tests/test_customer.py b/counter/tests/test_customer.py
index 2d7e1c60..dd7a5ed6 100644
--- a/counter/tests/test_customer.py
+++ b/counter/tests/test_customer.py
@@ -168,6 +168,7 @@ class TestStudentCard(TestCase):
cls.root = User.objects.get(username="root")
cls.counter = Counter.objects.get(id=2)
+ cls.ae_counter = Counter.objects.get(name="AE")
def setUp(self):
# Auto login on counter
@@ -191,94 +192,144 @@ class TestStudentCard(TestCase):
# Test card with mixed letters and numbers
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": "8B90734A802A8F", "action": "add_student_card"},
+ {"uid": "8B90734A802A8F"},
)
- self.assertContains(response, text="8B90734A802A8F")
+ assert response.status_code == 302
+ self.assertContains(self.client.get(response.url), text="8B90734A802A8F")
# Test card with only numbers
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": "04786547890123", "action": "add_student_card"},
+ {"uid": "04786547890123"},
)
- self.assertContains(response, text="04786547890123")
+ assert response.status_code == 302
+ self.assertContains(self.client.get(response.url), text="04786547890123")
# Test card with only letters
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": "ABCAAAFAAFAAAB", "action": "add_student_card"},
+ {"uid": "ABCAAAFAAFAAAB"},
)
- self.assertContains(response, text="ABCAAAFAAFAAAB")
+ assert response.status_code == 302
+ self.assertContains(self.client.get(response.url), text="ABCAAAFAAFAAAB")
def test_add_student_card_from_counter_fail(self):
# UID too short
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": "8B90734A802A8", "action": "add_student_card"},
- )
- self.assertContains(
- response, text="Ce n'est pas un UID de carte étudiante valide"
+ {"uid": "8B90734A802A8"},
)
+ self.assertContains(response, text="Cet UID est invalide")
# UID too long
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": "8B90734A802A8FA", "action": "add_student_card"},
+ {"uid": "8B90734A802A8FA"},
)
+ self.assertContains(response, text="Cet UID est invalide")
self.assertContains(
- response, text="Ce n'est pas un UID de carte étudiante valide"
+ response,
+ text="Assurez-vous que cette valeur comporte au plus 14 caractères (actuellement 15).",
)
# Test with already existing card
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": "9A89B82018B0A0", "action": "add_student_card"},
+ {"uid": "9A89B82018B0A0"},
)
+ self.assertContains(response, text="Cet UID est invalide")
self.assertContains(
- response, text="Ce n'est pas un UID de carte étudiante valide"
+ response, text="Un objet Student card avec ce champ Uid existe déjà."
)
# Test with lowercase
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": "8b90734a802a9f", "action": "add_student_card"},
- )
- self.assertContains(
- response, text="Ce n'est pas un UID de carte étudiante valide"
+ {"uid": "8b90734a802a9f"},
)
+ self.assertContains(response, text="Cet UID est invalide")
# Test with white spaces
response = self.client.post(
reverse(
- "counter:click",
- kwargs={"counter_id": self.counter.id, "user_id": self.sli.id},
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": self.counter.id,
+ "customer_id": self.sli.customer.pk,
+ },
),
- {"student_card_uid": " ", "action": "add_student_card"},
+ {"uid": " "},
)
- self.assertContains(
- response, text="Ce n'est pas un UID de carte étudiante valide"
+ self.assertContains(response, text="Cet UID est invalide")
+ self.assertContains(response, text="Ce champ est obligatoire.")
+
+ def test_add_student_card_from_counter_unauthorized(self):
+ # Send to a counter where you aren't logged in
+ self.client.post(
+ reverse("counter:logout", args=[self.counter.id]),
+ {"user_id": self.krophil.id},
)
+ def send_valid_request(client, counter_id):
+ return client.post(
+ reverse(
+ "counter:add_student_card_fragment",
+ kwargs={
+ "counter_id": counter_id,
+ "customer_id": self.sli.customer.pk,
+ },
+ ),
+ {"uid": "8B90734A802A8F"},
+ )
+
+ assert send_valid_request(self.client, self.counter.id).status_code == 403
+
+ # Send to a non bar counter
+ self.client.force_login(self.skia)
+ assert send_valid_request(self.client, self.ae_counter.id)
+
def test_delete_student_card_with_owner(self):
self.client.force_login(self.sli)
self.client.post(
diff --git a/counter/urls.py b/counter/urls.py
index a2732925..8b7417c7 100644
--- a/counter/urls.py
+++ b/counter/urls.py
@@ -45,6 +45,7 @@ from counter.views import (
RefillingDeleteView,
SellingDeleteView,
StudentCardDeleteView,
+ StudentCardFormFragmentView,
StudentCardFormView,
counter_login,
counter_logout,
@@ -73,6 +74,11 @@ urlpatterns = [
StudentCardFormView.as_view(),
name="add_student_card",
),
+ path(
+ "customer//card/add/counter//",
+ StudentCardFormFragmentView.as_view(),
+ name="add_student_card_fragment",
+ ),
path(
"customer//card/delete//",
StudentCardDeleteView.as_view(),
diff --git a/counter/views.py b/counter/views.py
index 9483d335..4176e3ba 100644
--- a/counter/views.py
+++ b/counter/views.py
@@ -53,14 +53,13 @@ from django.views.generic.edit import (
from accounting.models import CurrencyField
from core.utils import get_semester_code, get_start_of_semester
-from core.views import CanEditMixin, CanViewMixin, TabedViewMixin
+from core.views import AllowFragment, CanEditMixin, CanViewMixin, TabedViewMixin
from core.views.forms import LoginForm
from counter.forms import (
CashSummaryFormBase,
CounterEditForm,
EticketForm,
GetUserForm,
- NFCCardForm,
ProductEditForm,
RefillForm,
StudentCardForm,
@@ -330,7 +329,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
request.session["too_young"] = False
request.session["not_allowed"] = False
request.session["no_age"] = False
- request.session["not_valid_student_card_uid"] = False
if self.object.type != "BAR":
self.operator = request.user
elif self.customer_is_barman():
@@ -342,8 +340,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
action = parse_qs(request.body.decode()).get("action", [""])[0]
if action == "add_product":
self.add_product(request)
- elif action == "add_student_card":
- self.add_student_card(request)
elif action == "del_product":
self.del_product(request)
elif action == "refill":
@@ -480,23 +476,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
request.session.modified = True
return True
- def add_student_card(self, request):
- """Add a new student card on the customer account."""
- uid = str(request.POST["student_card_uid"])
- if not StudentCard.is_valid(uid):
- request.session["not_valid_student_card_uid"] = True
- return False
-
- if not (
- self.object.type == "BAR"
- and "counter_token" in request.session
- and request.session["counter_token"] == self.object.token
- and self.object.is_open
- ):
- raise PermissionDenied
- StudentCard(customer=self.customer, uid=uid).save()
- return True
-
def del_product(self, request):
"""Delete a product from the basket."""
pid = parse_qs(request.body.decode())["product_id"][0]
@@ -627,11 +606,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
product
)
kwargs["customer"] = self.customer
- kwargs["student_cards"] = self.customer.student_cards.all()
- kwargs["student_card_input"] = NFCCardForm()
kwargs["basket_total"] = self.sum_basket(self.request)
kwargs["refill_form"] = self.refill_form or RefillForm()
- kwargs["student_card_max_uid_size"] = StudentCard.UID_SIZE
kwargs["barmens_can_refill"] = self.object.can_refill()
return kwargs
@@ -1513,7 +1489,7 @@ class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListVie
return kwargs
-class StudentCardFormView(FormView):
+class StudentCardFormView(AllowFragment, FormView):
"""Add a new student card."""
form_class = StudentCardForm
@@ -1535,3 +1511,52 @@ class StudentCardFormView(FormView):
return reverse_lazy(
"core:user_prefs", kwargs={"user_id": self.customer.user.pk}
)
+
+
+class StudentCardFormFragmentView(FormView):
+ """
+ Add a new student card from a counter
+ This is a fragment only view which integrates with counter_click.jinja
+ """
+
+ form_class = StudentCardForm
+ template_name = "counter/add_student_card_fragment.jinja"
+
+ def dispatch(self, request, *args, **kwargs):
+ self.counter = get_object_or_404(
+ Counter.objects.annotate_is_open(), pk=kwargs["counter_id"]
+ )
+ self.customer = get_object_or_404(
+ Customer.objects.prefetch_related("student_cards"), pk=kwargs["customer_id"]
+ )
+ if not (
+ self.counter.type == "BAR"
+ and "counter_token" in request.session
+ and request.session["counter_token"] == self.counter.token
+ and self.counter.is_open
+ ):
+ raise PermissionDenied
+ return super().dispatch(request, *args, **kwargs)
+
+ def form_valid(self, form):
+ data = form.clean()
+ res = super(FormView, self).form_valid(form)
+ StudentCard(customer=self.customer, uid=data["uid"]).save()
+ return res
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["counter"] = self.counter
+ context["customer"] = self.customer
+ context["action"] = self.request.path
+ context["student_cards"] = self.customer.student_cards.all()
+ return context
+
+ def get_success_url(self, **kwargs):
+ return reverse_lazy(
+ "counter:add_student_card_fragment",
+ kwargs={
+ "customer_id": self.customer.pk,
+ "counter_id": self.counter.id,
+ },
+ )