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

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 

14 

15from sales import services 

16from sales.forms import ProductOrderForm 

17from sales.models.order import Order, OrderItem, Shift 

18 

19 

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" 

25 

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 

44 

45 

46@method_decorator(login_required, name="dispatch") 

47class PlaceOrderView(FormView): 

48 template_name = "sales/order_place.html" 

49 

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) 

61 

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 ) 

68 

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) 

95 

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 

101 

102 

103@method_decorator(login_required, name="dispatch") 

104class CancelOrderView(DeleteView): 

105 model = Order 

106 

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 

112 

113 def get_success_url(self): 

114 return reverse("sales:shift-detail", args=[self.object.shift.pk]) 

115 

116 

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") 

127 

128 order.payer = request.member 

129 order.save() 

130 

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") 

139 

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") 

149 

150 if order.total_amount == 0: 

151 messages.info(request, _("This order doesn't require payment.")) 

152 return redirect("index") 

153 

154 return render(request, "sales/order_payment.html", {"order": order})