Add tooltip on current registered card, allow barmen to delete cards and make card deletion a fragment

This commit is contained in:
Antoine Bartuccio 2024-12-10 23:48:46 +01:00
parent 466fe58763
commit 4975475e85
6 changed files with 107 additions and 32 deletions

View File

@ -42,6 +42,29 @@ body {
} }
} }
[tooltip] {
position: relative;
}
[tooltip]:before {
opacity: 0;
z-index: 1;
content: attr(tooltip);
background: $white-color;
color: $black-color;
border: 1px solid $black-color;
border-radius: 5px;
padding: 5px;
top: 1em;
position: absolute;
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;
@ -308,6 +331,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 +342,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;
} }

View File

@ -13,10 +13,13 @@
<em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em> <em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em>
{% else %} {% else %}
<p> <p>
{% trans %}Registered{% endtrans %} <i class="fa fa-check"></i> &nbsp; - &nbsp; {% trans %}Card registered{% endtrans %}</span>
<a href="{{ url('counter:delete_student_card', customer_id=customer.pk) }}"> <span tooltip="{% trans uid=customer.student_card.uid %}uid: {{ uid }} {% endtrans %}"><i class="fa fa-check" style="color: green"></i></span> &nbsp; - &nbsp;
{% trans %}Delete{% endtrans %} <button
</a> hx-get="{{ url('counter:delete_student_card', customer_id=customer.pk) }}"
hx-swap="outerHTML"
hx-target="#student_card_form"
>{% trans %}Delete{% endtrans %}</button>
</p> </p>
{% endif %} {% endif %}
</div> </div>

View File

@ -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>

View File

@ -198,8 +198,7 @@ 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"},
@ -222,6 +221,7 @@ class TestStudentCard(TestCase):
] ]
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},
@ -233,6 +233,7 @@ class TestStudentCard(TestCase):
) )
def test_add_student_card_from_counter(self): def test_add_student_card_from_counter(self):
self.login_in_counter()
for uid in ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"]: for uid in ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"]:
customer = subscriber_user.make().customer customer = subscriber_user.make().customer
response = self.client.post( response = self.client.post(
@ -251,6 +252,7 @@ class TestStudentCard(TestCase):
assert customer.student_card.uid == uid assert customer.student_card.uid == uid
def test_add_student_card_from_counter_fail(self): def test_add_student_card_from_counter_fail(self):
self.login_in_counter()
customer = subscriber_user.make().customer customer = subscriber_user.make().customer
for uid, error_msg in self.invalid_uids(): for uid, error_msg in self.invalid_uids():
response = self.client.post( response = self.client.post(
@ -269,25 +271,15 @@ class TestStudentCard(TestCase):
assert not hasattr(customer, "student_card") assert not hasattr(customer, "student_card")
def test_add_student_card_from_counter_unauthorized(self): def test_add_student_card_from_counter_unauthorized(self):
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(counter_id): def send_valid_request(counter_id):
return client.post( return self.client.post(
reverse( reverse(
"counter:add_student_card", kwargs={"customer_id": customer.pk} "counter:add_student_card", kwargs={"customer_id": self.customer.pk}
), ),
{"uid": "8B90734A802A8F"}, {"uid": "8B90734A802A8F"},
HTTP_REFERER=reverse( HTTP_REFERER=reverse(
"counter:click", "counter:click",
kwargs={"counter_id": counter_id, "user_id": customer.pk}, kwargs={"counter_id": counter_id, "user_id": self.customer.pk},
), ),
) )
@ -295,7 +287,7 @@ class TestStudentCard(TestCase):
assert send_valid_request(self.counter.id).status_code == 403 assert send_valid_request(self.counter.id).status_code == 403
# Send to a non bar counter # Send to a non bar counter
client.force_login(self.club_admin) self.client.force_login(self.club_admin)
assert send_valid_request(self.club_counter.id).status_code == 403 assert send_valid_request(self.club_counter.id).status_code == 403
def test_delete_student_card_with_owner(self): def test_delete_student_card_with_owner(self):
@ -322,6 +314,24 @@ class TestStudentCard(TestCase):
self.customer.customer.refresh_from_db() self.customer.customer.refresh_from_db()
assert not hasattr(self.customer.customer, "student_card") assert not hasattr(self.customer.customer, "student_card")
def test_delete_student_card_from_counter(self):
self.login_in_counter()
self.client.post(
reverse(
"counter:delete_student_card",
kwargs={"customer_id": self.customer.customer.pk},
),
http_referer=reverse(
"counter:click",
kwargs={
"counter_id": self.counter.id,
"user_id": self.customer.customer.pk,
},
),
)
self.customer.customer.refresh_from_db()
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""" """Test that non-admin users cannot delete student cards"""
self.client.force_login(self.subscriber) self.client.force_login(self.subscriber)
@ -336,7 +346,7 @@ class TestStudentCard(TestCase):
assert not hasattr(self.subscriber.customer, "student_card") 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):
users = [self.subscriber, self.board_admin, self.root] users = [self.customer, self.board_admin, self.root]
uids = ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"] uids = ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"]
for user, uid in itertools.product(users, uids): for user, uid in itertools.product(users, uids):
self.customer.customer.student_card.delete() self.customer.customer.student_card.delete()
@ -353,7 +363,7 @@ class TestStudentCard(TestCase):
self.customer.customer.refresh_from_db() self.customer.customer.refresh_from_db()
assert self.customer.customer.student_card.uid == uid assert self.customer.customer.student_card.uid == uid
self.assertContains(response, text="Enregistré") self.assertContains(response, text="Carte enregistrée")
def test_add_student_card_from_user_preferences_fail(self): def test_add_student_card_from_user_preferences_fail(self):
customer = subscriber_user.make() customer = subscriber_user.make()

View File

@ -22,22 +22,32 @@ 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"
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): def get_object(self, queryset=None):
if not hasattr(self.customer, "student_card"): if not hasattr(self.customer, "student_card"):
raise Http404( raise Http404(
@ -47,7 +57,9 @@ class StudentCardDeleteView(DeleteView, CanEditMixin):
return self.customer.student_card return self.customer.student_card
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
return reverse("core:user_prefs", kwargs={"user_id": self.customer.user_id}) return reverse(
"counter:add_student_card", kwargs={"customer_id": self.customer.pk}
)
class StudentCardFormView(FormView): class StudentCardFormView(FormView):

View File

@ -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:18 #: 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
@ -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"
@ -4044,8 +4047,13 @@ 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 #: counter/templates/counter/fragments/create_student_card.jinja:16
msgid "Registered" msgid "Card registered"
msgstr "Enregistré" 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
@ -4317,7 +4325,7 @@ msgstr "Administration des comptoirs"
msgid "Product types" msgid "Product types"
msgstr "Types de produit" msgstr "Types de produit"
#: counter/views/student_card.py:44 #: counter/views/student_card.py:54
#, python-format #, python-format
msgid "%(name)s has no registered student card" msgid "%(name)s has no registered student card"
msgstr "%(name)s n'a pas de carte étudiante enregistrée" msgstr "%(name)s n'a pas de carte étudiante enregistrée"