Coverage for website/sales/views.py: 56.14%
92 statements
« prev ^ index » next coverage.py v7.6.12, created at 2026-06-21 23:59 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2026-06-21 23:59 +0000
1from django.contrib import messages
2from django.contrib.auth.decorators import login_required
3from django.core.exceptions import PermissionDenied
4from django.db import transaction
5from django.db.models import Q
6from django.shortcuts import get_object_or_404, redirect, render
7from django.urls import reverse
8from django.utils import timezone
9from django.utils.decorators import method_decorator
10from django.utils.translation import gettext_lazy as _
11from django.views import View
12from django.views.generic import DeleteView, DetailView
13from django.views.generic.edit import FormView
15from sales import services
16from sales.forms import ProductOrderForm
17from sales.models.order import Order, OrderItem, Shift
20@method_decorator(login_required, name="dispatch")
21class ShiftDetailView(DetailView):
22 model = Shift
23 context_object_name = "shift"
24 template_name = "sales/shift_view.html"
26 def get_context_data(self, **kwargs):
27 context = super().get_context_data(**kwargs)
28 shift = context["shift"]
29 queryset = Order.objects.filter(shift=shift).order_by("-created_at")
30 queryset = queryset.select_properties(
31 "total_amount", "subtotal", "num_items", "age_restricted"
32 )
33 queryset = queryset.prefetch_related(
34 "shift", "shift__event", "shift__product_list"
35 )
36 queryset = queryset.prefetch_related(
37 "order_items", "order_items__product", "order_items__product__product"
38 )
39 queryset = queryset.prefetch_related("payment")
40 context["orders"] = queryset.filter(
41 Q(payer=self.request.member) | Q(created_by=self.request.member)
42 )
43 return context
46@method_decorator(login_required, name="dispatch")
47class PlaceOrderView(FormView):
48 template_name = "sales/order_place.html"
50 def dispatch(self, request, *args, **kwargs):
51 self.shift = get_object_or_404(Shift, pk=kwargs["pk"])
52 if not self.shift.user_orders_allowed and request.method == "POST":
53 # Forbid POSTing when not in the correct time period
54 # or when only admins can manage this shift's orders
55 raise PermissionDenied
56 if not request.member.can_attend_events:
57 raise PermissionDenied
58 # TODO: if a shift belongs to an event, should we restrict self-ordering to event participants?
59 # (at least if the event requires registration, of course)
60 return super().dispatch(request, *args, **kwargs)
62 def get_form(self, form_class=None):
63 return ProductOrderForm(
64 self.shift.product_list,
65 services.is_adult(self.request.member),
66 **self.get_form_kwargs(),
67 )
69 @transaction.atomic
70 def form_valid(self, form):
71 order = Order(
72 created_at=timezone.now(),
73 created_by=self.request.member,
74 shift=self.shift,
75 payment=None,
76 discount=0.00,
77 payer=self.request.member,
78 )
79 order.save()
80 for fieldname, amount in form.cleaned_data.items():
81 if amount is None:
82 continue
83 if (
84 not services.is_adult(self.request.member)
85 and form.fields[fieldname].get_productlistitem().product.age_restricted
86 ):
87 raise PermissionDenied
88 item = OrderItem(
89 product=form.fields[fieldname].get_productlistitem(),
90 order=order,
91 amount=amount,
92 )
93 item.save()
94 return redirect("sales:shift-detail", pk=self.shift.pk)
96 def get_context_data(self, **kwargs):
97 ctx = super().get_context_data(**kwargs)
98 ctx["shift"] = self.shift
99 ctx["date_now"] = timezone.now()
100 return ctx
103@method_decorator(login_required, name="dispatch")
104class CancelOrderView(DeleteView):
105 model = Order
107 def get_object(self, queryset=None):
108 order = super().get_object(queryset)
109 if not order.user_can_modify(self.request.member):
110 raise PermissionDenied
111 return order
113 def get_success_url(self):
114 return reverse("sales:shift-detail", args=[self.object.shift.pk])
117@method_decorator(login_required, name="dispatch")
118class OrderPaymentView(View):
119 def get(self, request, *args, **kwargs):
120 order = get_object_or_404(Order, pk=kwargs["pk"])
121 if order.payment:
122 messages.warning(request, _("This order was already paid for."))
123 return redirect("index")
124 if order.payer is not None and order.payer != request.member:
125 messages.warning(request, _("This order is not yours."))
126 return redirect("index")
128 order.payer = request.member
129 order.save()
131 if order.age_restricted and not services.is_adult(request.member):
132 messages.error(
133 request,
134 _(
135 "The age restrictions on this order do not allow you to pay for this order."
136 ),
137 )
138 return redirect("index")
140 if ( 140 ↛ 145line 140 didn't jump to line 145 because the condition on line 140 was never true
141 order.age_restricted
142 and services.is_adult(request.member)
143 and order.total_amount == 0
144 ):
145 messages.success(
146 request, _("You have successfully identified yourself for this order.")
147 )
148 return redirect("index")
150 if order.total_amount == 0:
151 messages.info(request, _("This order doesn't require payment."))
152 return redirect("index")
154 return render(request, "sales/order_payment.html", {"order": order})