diff --git a/counter/api.py b/counter/api.py index f3f0f101..d58f0154 100644 --- a/counter/api.py +++ b/counter/api.py @@ -21,11 +21,12 @@ from ninja_extra import ControllerBase, api_controller, paginate, route from ninja_extra.pagination import PageNumberPaginationExtra from ninja_extra.schemas import PaginatedResponseSchema -from core.api_permissions import CanAccessLookup, CanView, IsRoot -from counter.models import Counter, Product +from core.api_permissions import CanAccessLookup, CanView, IsLoggedInCounter, IsRoot +from counter.models import Counter, Customer, Product from counter.schemas import ( CounterFilterSchema, CounterSchema, + CustomerBalance, ProductSchema, SimplifiedCounterSchema, ) @@ -60,6 +61,13 @@ class CounterController(ControllerBase): return filters.filter(Counter.objects.all()) +@api_controller("/customer") +class CustomerController(ControllerBase): + @route.get("/balance", response=CustomerBalance, permissions=[IsLoggedInCounter]) + def get_balance(self, customer_id: int): + return self.get_object_or_exception(Customer, pk=customer_id) + + @api_controller("/product") class ProductController(ControllerBase): @route.get( diff --git a/counter/schemas.py b/counter/schemas.py index ec1a842d..4fbbc712 100644 --- a/counter/schemas.py +++ b/counter/schemas.py @@ -4,7 +4,7 @@ from annotated_types import MinLen from ninja import Field, FilterSchema, ModelSchema from core.schemas import SimpleUserSchema -from counter.models import Counter, Product +from counter.models import Counter, Customer, Product class CounterSchema(ModelSchema): @@ -16,6 +16,12 @@ class CounterSchema(ModelSchema): fields = ["id", "name", "type", "club", "products"] +class CustomerBalance(ModelSchema): + class Meta: + model = Customer + fields = ["amount"] + + class CounterFilterSchema(FilterSchema): search: Annotated[str, MinLen(1)] = Field(None, q="name__icontains") diff --git a/counter/static/counter/js/counter_click.js b/counter/static/bundled/counter/counter-click-index.ts similarity index 100% rename from counter/static/counter/js/counter_click.js rename to counter/static/bundled/counter/counter-click-index.ts diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index 3df77555..65c7d9ca 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -6,7 +6,7 @@ {% endblock %} {% block additional_js %} - + {% endblock %} {% block info_boxes %} @@ -28,7 +28,7 @@
{% trans %}Customer{% endtrans %}
{{ user_mini_profile(customer.user) }} {{ user_subscription(customer.user) }} -

{% trans %}Amount: {% endtrans %}{{ customer.amount }} €

+

{% trans %}Amount: {% endtrans %}

{% if counter.type == 'BAR' %}
{% trans %}Student card{% endtrans %}
@@ -105,16 +105,12 @@ - {% if (counter.type == 'BAR' and barmens_can_refill) %} + {% if refilling_fragment %}
{% trans %}Refilling{% endtrans %}
-
-
- {% csrf_token %} - {{ refill_form.as_p() }} - - -
+
+ {{ refilling_fragment }}
{% endif %}
@@ -155,9 +151,6 @@ {% block script %} {{ super() }} {% endblock script %} \ No newline at end of file diff --git a/counter/templates/counter/fragments/create_refill.jinja b/counter/templates/counter/fragments/create_refill.jinja new file mode 100644 index 00000000..7de612a3 --- /dev/null +++ b/counter/templates/counter/fragments/create_refill.jinja @@ -0,0 +1,9 @@ +
+ {% csrf_token %} + {{ form.as_p() }} + +
diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index 855b43f6..2574865f 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -67,17 +67,24 @@ class TestCounter(TestCase): {"code": self.richard.customer.account_id, "counter_token": counter_token}, ) counter_url = response.get("location") - response = self.client.get(response.get("location")) + refill_url = reverse( + "counter:refilling_create", + kwargs={"customer_id": self.richard.customer.pk}, + ) + + response = self.client.get( + response.get("location"), + ) assert ">Richard Batsbak/", CounterMain.as_view(), name="details"), path("/click//", CounterClick.as_view(), name="click"), + path( + "refill//", + RefillingCreateView.as_view(), + name="refilling_create", + ), path( "/last_ops/", CounterLastOperationsView.as_view(), diff --git a/counter/views/click.py b/counter/views/click.py index 65e889ff..1d87ac8b 100644 --- a/counter/views/click.py +++ b/counter/views/click.py @@ -24,11 +24,13 @@ from django.http import Http404, HttpResponseRedirect, JsonResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ -from django.views.generic import DetailView +from django.views.generic import DetailView, FormView +from core.utils import FormFragmentTemplateData from core.views import CanViewMixin from counter.forms import RefillForm from counter.models import Counter, Customer, Product, Selling +from counter.utils import is_logged_in_counter from counter.views.mixins import CounterTabsMixin from counter.views.student_card import StudentCardFormView @@ -100,7 +102,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session["too_young"] = False request.session["not_allowed"] = False request.session["no_age"] = False - self.refill_form = None ret = super().get(request, *args, **kwargs) if (self.object.type != "BAR" and not request.user.is_authenticated) or ( self.object.type == "BAR" and len(self.object.barmen_list) == 0 @@ -111,7 +112,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def post(self, request, *args, **kwargs): """Handle the many possibilities of the post request.""" self.object = self.get_object() - self.refill_form = None if (self.object.type != "BAR" and not request.user.is_authenticated) or ( self.object.type == "BAR" and len(self.object.barmen_list) < 1 ): # Check that at least one barman is logged in @@ -148,8 +148,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): self.add_product(request) elif action == "del_product": self.del_product(request) - elif action == "refill": - self.refill(request) elif action == "code": return self.parse_code(request) elif action == "cancel": @@ -383,19 +381,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): reverse_lazy("counter:details", args=self.args, kwargs=kwargs) ) - def refill(self, request): - """Refill the customer's account.""" - if not self.object.can_refill(): - raise PermissionDenied - form = RefillForm(request.POST) - if form.is_valid(): - form.instance.counter = self.object - form.instance.operator = self.operator - form.instance.customer = self.customer - form.instance.save() - else: - self.refill_form = form - def get_context_data(self, **kwargs): """Add customer to the context.""" kwargs = super().get_context_data(**kwargs) @@ -413,9 +398,77 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): ) kwargs["customer"] = self.customer 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_fragment"] = StudentCardFormView.get_template_data( self.customer ).render(self.request) + + if self.object.can_refill(): + kwargs["refilling_fragment"] = RefillingCreateView.get_template_data( + self.customer + ).render(self.request) return kwargs + + +class RefillingCreateView(FormView): + """This is a fragment only view which integrates with counter_click.jinja""" + + form_class = RefillForm + template_name = "counter/fragments/create_refill.jinja" + + @classmethod + def get_template_data( + cls, customer: Customer, *, form_instance: form_class | None = None + ) -> FormFragmentTemplateData[form_class]: + return FormFragmentTemplateData[cls.form_class]( + form=form_instance if form_instance else cls.form_class(), + template=cls.template_name, + context={ + "action": reverse_lazy( + "counter:refilling_create", kwargs={"customer_id": customer.pk} + ), + }, + ) + + def dispatch(self, request, *args, **kwargs): + self.customer: Customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) + if not self.customer.can_buy: + raise Http404 + + if not is_logged_in_counter(request): + raise PermissionDenied + + self.counter: Counter = get_object_or_404( + Counter, token=request.session["counter_token"] + ) + + if not self.counter.can_refill(): + raise PermissionDenied + + if self.customer_is_barman(): + self.operator = self.customer.user + else: + self.operator = self.counter.get_random_barman() + + return super().dispatch(request, *args, **kwargs) + + def customer_is_barman(self) -> bool: + barmen = self.counter.barmen_list + return self.counter.type == "BAR" and self.customer.user in barmen + + def form_valid(self, form): + res = super().form_valid(form) + form.clean() + form.instance.counter = self.counter + form.instance.operator = self.operator + form.instance.customer = self.customer + form.instance.save() + return res + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + data = self.get_template_data(self.customer, form_instance=context["form"]) + context.update(data.context) + return context + + def get_success_url(self, **kwargs): + return self.request.path diff --git a/counter/views/student_card.py b/counter/views/student_card.py index 35226e95..952f65eb 100644 --- a/counter/views/student_card.py +++ b/counter/views/student_card.py @@ -70,11 +70,11 @@ class StudentCardFormView(FormView): @classmethod def get_template_data( - cls, customer: Customer - ) -> FormFragmentTemplateData[StudentCardForm]: + cls, customer: Customer, *, form_instance: form_class | None = None + ) -> FormFragmentTemplateData[form_class]: """Get necessary data to pre-render the fragment""" - return FormFragmentTemplateData( - form=cls.form_class(), + return FormFragmentTemplateData[cls.form_class]( + form=form_instance if form_instance else cls.form_class(), template=cls.template_name, context={ "action": reverse( @@ -105,7 +105,7 @@ class StudentCardFormView(FormView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - data = self.get_template_data(self.customer) + data = self.get_template_data(self.customer, form_instance=context["form"]) context.update(data.context) return context