Create a generic form fragment renderer

This commit is contained in:
Antoine Bartuccio 2024-12-08 11:45:16 +01:00
parent 66d2dc74e7
commit de7aa6f6a6
7 changed files with 150 additions and 158 deletions

View File

@ -36,19 +36,11 @@
{% if student_card %} {% if student_card %}
{% with {{ student_card }}
form=student_card.form, <p class="justify">
action=student_card.context.action, {% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually
customer=student_card.context.customer, add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %}
student_cards=student_card.context.student_cards </p>
%} {% endif %}
{% include student_card.template %} </div>
{% endwith %}
<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 %}
</p>
{% endif %}
</div>
{% endblock %} {% endblock %}

View File

@ -13,22 +13,41 @@
# #
# #
from dataclasses import dataclass
from datetime import date from datetime import date
# Image utils # Image utils
from io import BytesIO from io import BytesIO
from typing import Optional from typing import Any
import PIL import PIL
from django.conf import settings from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.forms import BaseForm
from django.http import HttpRequest from django.http import HttpRequest
from django.template.loader import render_to_string
from django.utils.html import SafeString
from django.utils.timezone import localdate from django.utils.timezone import localdate
from PIL import ExifTags from PIL import ExifTags
from PIL.Image import Image, Resampling from PIL.Image import Image, Resampling
def get_start_of_semester(today: Optional[date] = None) -> date: @dataclass
class FormFragmentTemplateData[T: BaseForm]:
"""Dataclass used to pre-render form fragments"""
form: T
template: str
context: dict[str, Any]
def render(self, request: HttpRequest) -> SafeString:
# Request is needed for csrf_tokens
return render_to_string(
self.template, context={"form": self.form, **self.context}, request=request
)
def get_start_of_semester(today: date | None = None) -> date:
"""Return the date of the start of the semester of the given date. """Return the date of the start of the semester of the given date.
If no date is given, return the start date of the current semester. If no date is given, return the start date of the current semester.
@ -58,7 +77,7 @@ def get_start_of_semester(today: Optional[date] = None) -> date:
return autumn.replace(year=autumn.year - 1) return autumn.replace(year=autumn.year - 1)
def get_semester_code(d: Optional[date] = None) -> str: def get_semester_code(d: date | None = None) -> str:
"""Return the semester code of the given date. """Return the semester code of the given date.
If no date is given, return the semester code of the current semester. If no date is given, return the semester code of the current semester.

View File

@ -578,8 +578,8 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
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"] = StudentCardFormView.get_template_data(
self.request, self.object.customer self.object.customer
).as_dict() ).render(self.request)
return kwargs return kwargs

View File

@ -31,131 +31,124 @@
<p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p> <p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p>
{% if counter.type == 'BAR' %} {% if counter.type == 'BAR' %}
{% with {{ student_card }}
form=student_card.form, {% endif %}
action=student_card.context.action, </div>
customer=student_card.context.customer,
student_cards=student_card.context.student_cards
%}
{% include student_card.template %}
{% endwith %}
{% endif %}
</div>
<div id="click_form"> <div id="click_form">
<h5>{% trans %}Selling{% endtrans %}</h5> <h5>{% trans %}Selling{% endtrans %}</h5>
<div> <div>
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %} {% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %}
{# Formulaire pour rechercher un produit en tapant son code dans une barre de recherche #} {# Formulaire pour rechercher un produit en tapant son code dans une barre de recherche #}
<form method="post" action="" <form method="post" action=""
class="code_form" @submit.prevent="handleCode"> class="code_form" @submit.prevent="handleCode">
{% csrf_token %}
<input type="hidden" name="action" value="code">
<label for="code_field"></label>
<input type="text" name="code" value="" class="focus" id="code_field"/>
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>
<template x-for="error in errors">
<div class="alert alert-red" x-text="error">
</div>
</template>
<p>{% trans %}Basket: {% endtrans %}</p>
<ul>
<template x-for="[id, item] in Object.entries(basket)" :key="id">
<div>
<form method="post" action="" class="inline del_product_form"
@submit.prevent="handleAction">
{% csrf_token %}
<input type="hidden" name="action" value="del_product">
<input type="hidden" name="product_id" :value="id">
<input type="submit" value="-"/>
</form>
<span x-text="item['qty'] + item['bonus_qty']"></span>
<form method="post" action="" class="inline add_product_form"
@submit.prevent="handleAction">
{% csrf_token %}
<input type="hidden" name="action" value="add_product">
<input type="hidden" name="product_id" :value="id">
<input type="submit" value="+">
</form>
<span x-text="products[id].name"></span> :
<span x-text="(item['qty'] * item['price'] / 100)
.toLocaleString(undefined, { minimumFractionDigits: 2 })">
</span> €
<template x-if="item['bonus_qty'] > 0">P</template>
</div>
</template>
</ul>
<p>
<strong>Total: </strong>
<strong x-text="sumBasket().toLocaleString(undefined, { minimumFractionDigits: 2 })"></strong>
<strong> €</strong>
</p>
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
<input type="hidden" name="action" value="finish">
<input type="submit" value="{% trans %}Finish{% endtrans %}"/>
</form>
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
<input type="hidden" name="action" value="cancel">
<input type="submit" value="{% trans %}Cancel{% endtrans %}"/>
</form>
</div>
{% if (counter.type == 'BAR' and barmens_can_refill) %}
<h5>{% trans %}Refilling{% endtrans %}</h5>
<div>
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
{{ refill_form.as_p() }}
<input type="hidden" name="action" value="refill">
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>
</div>
{% endif %}
</div>
<div id="products">
<ul>
{% for category in categories.keys() -%}
<li><a href="#cat_{{ category|slugify }}">{{ category }}</a></li>
{%- endfor %}
</ul>
{% for category in categories.keys() -%}
<div id="cat_{{ category|slugify }}">
<h5>{{ category }}</h5>
{% for p in categories[category] -%}
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}"
class="form_button add_product_form" @submit.prevent="handleAction">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="action" value="add_product"> <input type="hidden" name="action" value="code">
<input type="hidden" name="product_id" value="{{ p.id }}"> <label for="code_field"></label>
<button type="submit"> <input type="text" name="code" value="" class="focus" id="code_field"/>
<strong>{{ p.name }}</strong> <input type="submit" value="{% trans %}Go{% endtrans %}"/>
{% if p.icon %}
<img src="{{ p.icon.url }}" alt="image de {{ p.name }}"/>
{% else %}
<img src="{{ static('core/img/na.gif') }}" alt="image de {{ p.name }}"/>
{% endif %}
<span>{{ p.price }} €<br>{{ p.code }}</span>
</button>
</form> </form>
<template x-for="error in errors">
<div class="alert alert-red" x-text="error">
</div>
</template>
<p>{% trans %}Basket: {% endtrans %}</p>
<ul>
<template x-for="[id, item] in Object.entries(basket)" :key="id">
<div>
<form method="post" action="" class="inline del_product_form"
@submit.prevent="handleAction">
{% csrf_token %}
<input type="hidden" name="action" value="del_product">
<input type="hidden" name="product_id" :value="id">
<input type="submit" value="-"/>
</form>
<span x-text="item['qty'] + item['bonus_qty']"></span>
<form method="post" action="" class="inline add_product_form"
@submit.prevent="handleAction">
{% csrf_token %}
<input type="hidden" name="action" value="add_product">
<input type="hidden" name="product_id" :value="id">
<input type="submit" value="+">
</form>
<span x-text="products[id].name"></span> :
<span x-text="(item['qty'] * item['price'] / 100)
.toLocaleString(undefined, { minimumFractionDigits: 2 })">
</span> €
<template x-if="item['bonus_qty'] > 0">P</template>
</div>
</template>
</ul>
<p>
<strong>Total: </strong>
<strong x-text="sumBasket().toLocaleString(undefined, { minimumFractionDigits: 2 })"></strong>
<strong> €</strong>
</p>
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
<input type="hidden" name="action" value="finish">
<input type="submit" value="{% trans %}Finish{% endtrans %}"/>
</form>
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
<input type="hidden" name="action" value="cancel">
<input type="submit" value="{% trans %}Cancel{% endtrans %}"/>
</form>
</div>
{% if (counter.type == 'BAR' and barmens_can_refill) %}
<h5>{% trans %}Refilling{% endtrans %}</h5>
<div>
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
{{ refill_form.as_p() }}
<input type="hidden" name="action" value="refill">
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>
</div>
{% endif %}
</div>
<div id="products">
<ul>
{% for category in categories.keys() -%}
<li><a href="#cat_{{ category|slugify }}">{{ category }}</a></li>
{%- endfor %}
</ul>
{% for category in categories.keys() -%}
<div id="cat_{{ category|slugify }}">
<h5>{{ category }}</h5>
{% for p in categories[category] -%}
<form method="post"
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}"
class="form_button add_product_form" @submit.prevent="handleAction">
{% csrf_token %}
<input type="hidden" name="action" value="add_product">
<input type="hidden" name="product_id" value="{{ p.id }}">
<button type="submit">
<strong>{{ p.name }}</strong>
{% if p.icon %}
<img src="{{ p.icon.url }}" alt="image de {{ p.name }}"/>
{% else %}
<img src="{{ static('core/img/na.gif') }}" alt="image de {{ p.name }}"/>
{% endif %}
<span>{{ p.price }} €<br>{{ p.code }}</span>
</button>
</form>
{%- endfor %}
</div>
{%- endfor %} {%- endfor %}
</div> </div>
{%- endfor %} </div>
</div>
</div>
{% endblock content %} {% endblock content %}
{% block script %} {% block script %}

View File

@ -7,7 +7,7 @@
> >
{% csrf_token %} {% csrf_token %}
{{ form.as_p() }} {{ form.as_p() }}
<button>{% trans %}Go{% endtrans %}</button> <input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form> </form>
<h6>{% trans %}Registered cards{% endtrans %}</h6> <h6>{% trans %}Registered cards{% endtrans %}</h6>

View File

@ -416,6 +416,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
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"] = StudentCardFormView.get_template_data(
self.request, self.customer self.customer
).as_dict() ).render(self.request)
return kwargs return kwargs

View File

@ -14,31 +14,19 @@
# #
from dataclasses import asdict, dataclass
from typing import Any
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpRequest from django.http import HttpRequest
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_lazy
from django.views.generic.edit import DeleteView, FormView from django.views.generic.edit import DeleteView, FormView
from core.utils import FormFragmentTemplateData
from core.views import CanEditMixin from core.views import CanEditMixin
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
@dataclass
class StudentCardTemplateData:
form: StudentCardForm
template: str
context: dict[str, Any]
def as_dict(self) -> dict[str, Any]:
return asdict(self)
class StudentCardDeleteView(DeleteView, CanEditMixin): class StudentCardDeleteView(DeleteView, CanEditMixin):
"""View used to delete a card from a user.""" """View used to delete a card from a user."""
@ -64,10 +52,10 @@ class StudentCardFormView(FormView):
@classmethod @classmethod
def get_template_data( def get_template_data(
cls, request: HttpRequest, customer: Customer cls, customer: Customer
) -> StudentCardTemplateData: ) -> FormFragmentTemplateData[form_class]:
"""Get necessary data to pre-render the fragment""" """Get necessary data to pre-render the fragment"""
return StudentCardTemplateData( return FormFragmentTemplateData[cls.form_class](
form=cls.form_class(), form=cls.form_class(),
template=cls.template_name, template=cls.template_name,
context={ context={
@ -99,7 +87,7 @@ class StudentCardFormView(FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
data = self.get_template_data(self.request, self.customer) data = self.get_template_data(self.customer)
context.update(data.context) context.update(data.context)
return context return context