You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

views.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import csv
  2. from django.conf import settings
  3. from django.contrib import messages
  4. from django.db import transaction
  5. from django.db.models import Count
  6. from django.http import HttpResponse
  7. from django.shortcuts import get_object_or_404, redirect
  8. from django.urls import reverse, reverse_lazy
  9. from django.utils import timezone
  10. from django.utils.translation import gettext_lazy as _
  11. from django.views import generic
  12. from oscar.core.loading import get_class, get_model
  13. from oscar.core.utils import slugify
  14. from oscar.views import sort_queryset
  15. VoucherForm = get_class('dashboard.vouchers.forms', 'VoucherForm')
  16. VoucherSetForm = get_class('dashboard.vouchers.forms', 'VoucherSetForm')
  17. VoucherSetSearchForm = get_class('dashboard.vouchers.forms', 'VoucherSetSearchForm')
  18. VoucherSearchForm = get_class('dashboard.vouchers.forms', 'VoucherSearchForm')
  19. Voucher = get_model('voucher', 'Voucher')
  20. VoucherSet = get_model('voucher', 'VoucherSet')
  21. OrderDiscount = get_model('order', 'OrderDiscount')
  22. class VoucherListView(generic.ListView):
  23. model = Voucher
  24. context_object_name = 'vouchers'
  25. template_name = 'oscar/dashboard/vouchers/voucher_list.html'
  26. form_class = VoucherSearchForm
  27. paginate_by = settings.OSCAR_DASHBOARD_ITEMS_PER_PAGE
  28. def get_queryset(self):
  29. self.search_filters = []
  30. qs = self.model._default_manager.all()
  31. qs = qs.annotate(num_offers=Count('offers'))
  32. qs = sort_queryset(qs, self.request,
  33. ['num_basket_additions', 'num_orders', 'num_offers',
  34. 'date_created'],
  35. '-date_created')
  36. # If form is not submitted, perform a default filter, and return early
  37. if not self.request.GET:
  38. self.form = self.form_class(initial={'in_set': False})
  39. # This form is exactly the same as the other one, apart from having
  40. # fields with different IDs, so that they are unique within the page
  41. # (non-unique field IDs also break Select2)
  42. self.advanced_form = self.form_class(initial={'in_set': False}, auto_id='id_advanced_%s')
  43. self.search_filters.append(_("Not in a set"))
  44. return qs.filter(voucher_set__isnull=True)
  45. self.form = self.form_class(self.request.GET)
  46. # This form is exactly the same as the other one, apart from having
  47. # fields with different IDs, so that they are unique within the page
  48. # (non-unique field IDs also break Select2)
  49. self.advanced_form = self.form_class(self.request.GET, auto_id='id_advanced_%s')
  50. if not all([self.form.is_valid(), self.advanced_form.is_valid()]):
  51. return qs
  52. name = self.form.cleaned_data['name']
  53. code = self.form.cleaned_data['code']
  54. offer_name = self.form.cleaned_data['offer_name']
  55. is_active = self.form.cleaned_data['is_active']
  56. in_set = self.form.cleaned_data['in_set']
  57. has_offers = self.form.cleaned_data['has_offers']
  58. if name:
  59. qs = qs.filter(name__icontains=name)
  60. self.search_filters.append(_('Name matches "%s"') % name)
  61. if code:
  62. qs = qs.filter(code=code)
  63. self.search_filters.append(_('Code is "%s"') % code)
  64. if offer_name:
  65. qs = qs.filter(offers__name__icontains=offer_name)
  66. self.search_filters.append(_('Offer name matches "%s"') % offer_name)
  67. if is_active is not None:
  68. now = timezone.now()
  69. if is_active:
  70. qs = qs.filter(start_datetime__lte=now, end_datetime__gte=now)
  71. self.search_filters.append(_("Is active"))
  72. else:
  73. qs = qs.filter(end_datetime__lt=now)
  74. self.search_filters.append(_("Is inactive"))
  75. if in_set is not None:
  76. qs = qs.filter(voucher_set__isnull=not in_set)
  77. self.search_filters.append(_("In a set") if in_set else _("Not in a set"))
  78. if has_offers is not None:
  79. qs = qs.filter(offers__isnull=not has_offers).distinct()
  80. self.search_filters.append(_("Has offers") if has_offers else _("Has no offers"))
  81. return qs
  82. def get_context_data(self, **kwargs):
  83. ctx = super().get_context_data(**kwargs)
  84. ctx['form'] = self.form
  85. ctx['advanced_form'] = self.advanced_form
  86. ctx['search_filters'] = self.search_filters
  87. return ctx
  88. class VoucherCreateView(generic.CreateView):
  89. model = Voucher
  90. template_name = 'oscar/dashboard/vouchers/voucher_form.html'
  91. form_class = VoucherForm
  92. success_url = reverse_lazy('dashboard:voucher-list')
  93. def get_context_data(self, **kwargs):
  94. ctx = super().get_context_data(**kwargs)
  95. ctx['title'] = _('Create voucher')
  96. return ctx
  97. def get_initial(self):
  98. initial = super().get_initial()
  99. initial['start_datetime'] = timezone.now()
  100. return initial
  101. @transaction.atomic()
  102. def form_valid(self, form):
  103. response = super().form_valid(form)
  104. self.object.offers.add(*form.cleaned_data['offers'])
  105. return response
  106. def get_success_url(self):
  107. messages.success(self.request, _("Voucher created"))
  108. return super().get_success_url()
  109. class VoucherStatsView(generic.DetailView):
  110. model = Voucher
  111. template_name = 'oscar/dashboard/vouchers/voucher_detail.html'
  112. context_object_name = 'voucher'
  113. def get_context_data(self, **kwargs):
  114. ctx = super().get_context_data(**kwargs)
  115. discounts = OrderDiscount.objects.filter(voucher_id=self.object.id)
  116. discounts = discounts.order_by('-order__date_placed')
  117. ctx['discounts'] = discounts
  118. return ctx
  119. class VoucherUpdateView(generic.UpdateView):
  120. template_name = 'oscar/dashboard/vouchers/voucher_form.html'
  121. context_object_name = 'voucher'
  122. model = Voucher
  123. form_class = VoucherForm
  124. success_url = reverse_lazy('dashboard:voucher-list')
  125. def dispatch(self, request, *args, **kwargs):
  126. voucher_set = self.get_object().voucher_set
  127. if voucher_set is not None:
  128. messages.warning(request, _("The voucher can only be edited as part of its set"))
  129. return redirect('dashboard:voucher-set-update', pk=voucher_set.pk)
  130. return super().dispatch(request, *args, **kwargs)
  131. def get_context_data(self, **kwargs):
  132. ctx = super().get_context_data(**kwargs)
  133. ctx['title'] = self.object.name
  134. return ctx
  135. def get_initial(self):
  136. initial = super().get_initial()
  137. initial['offers'] = self.object.offers.all()
  138. return initial
  139. @transaction.atomic()
  140. def form_valid(self, form):
  141. response = super().form_valid(form)
  142. self.object.offers.set(form.cleaned_data['offers'])
  143. return response
  144. def get_success_url(self):
  145. messages.success(self.request, _("Voucher updated"))
  146. return super().get_success_url()
  147. class VoucherDeleteView(generic.DeleteView):
  148. model = Voucher
  149. template_name = 'oscar/dashboard/vouchers/voucher_delete.html'
  150. context_object_name = 'voucher'
  151. @transaction.atomic
  152. def delete(self, request, *args, **kwargs):
  153. response = super().delete(request, *args, **kwargs)
  154. if self.object.voucher_set is not None:
  155. self.object.voucher_set.update_count()
  156. return response
  157. def get_success_url(self):
  158. messages.warning(self.request, _("Voucher deleted"))
  159. if self.object.voucher_set is not None:
  160. return reverse('dashboard:voucher-set-detail', kwargs={'pk': self.object.voucher_set.pk})
  161. else:
  162. return reverse('dashboard:voucher-list')
  163. class VoucherSetCreateView(generic.CreateView):
  164. model = VoucherSet
  165. template_name = 'oscar/dashboard/vouchers/voucher_set_form.html'
  166. form_class = VoucherSetForm
  167. def get_context_data(self, **kwargs):
  168. ctx = super().get_context_data(**kwargs)
  169. ctx['title'] = _('Create voucher set')
  170. return ctx
  171. def get_initial(self):
  172. initial = super().get_initial()
  173. initial['start_datetime'] = timezone.now()
  174. return initial
  175. def get_success_url(self):
  176. messages.success(self.request, _("Voucher set created"))
  177. return reverse('dashboard:voucher-set-list')
  178. class VoucherSetUpdateView(generic.UpdateView):
  179. template_name = 'oscar/dashboard/vouchers/voucher_set_form.html'
  180. model = VoucherSet
  181. context_object_name = 'voucher_set'
  182. form_class = VoucherSetForm
  183. def get_context_data(self, **kwargs):
  184. ctx = super().get_context_data(**kwargs)
  185. ctx['title'] = self.object.name
  186. return ctx
  187. def get_initial(self):
  188. initial = super().get_initial()
  189. # All vouchers in the set have the same "usage" and "offers", so we use
  190. # the first one
  191. voucher = self.object.vouchers.first()
  192. if voucher is not None:
  193. initial['usage'] = voucher.usage
  194. initial['offers'] = voucher.offers.all()
  195. return initial
  196. def get_success_url(self):
  197. messages.success(self.request, _("Voucher updated"))
  198. return reverse('dashboard:voucher-set-detail', kwargs={'pk': self.object.pk})
  199. class VoucherSetDetailView(generic.ListView):
  200. model = Voucher
  201. context_object_name = 'vouchers'
  202. template_name = 'oscar/dashboard/vouchers/voucher_set_detail.html'
  203. form_class = VoucherSetSearchForm
  204. paginate_by = 50
  205. def dispatch(self, request, *args, **kwargs):
  206. self.voucher_set = get_object_or_404(VoucherSet, pk=kwargs['pk'])
  207. return super().dispatch(request, *args, **kwargs)
  208. def get_queryset(self):
  209. self.search_filters = []
  210. qs = (
  211. self.model.objects
  212. .filter(voucher_set=self.voucher_set)
  213. .order_by('-date_created'))
  214. qs = sort_queryset(qs, self.request,
  215. ['num_basket_additions', 'num_orders',
  216. 'date_created'],
  217. '-date_created')
  218. # If form not submitted, return early
  219. is_form_submitted = (
  220. 'name' in self.request.GET or 'code' in self.request.GET
  221. )
  222. if not is_form_submitted:
  223. self.form = self.form_class()
  224. return qs
  225. self.form = self.form_class(self.request.GET)
  226. if not self.form.is_valid():
  227. return qs
  228. data = self.form.cleaned_data
  229. if data['code']:
  230. qs = qs.filter(code__icontains=data['code'])
  231. self.search_filters.append(_('Code matches "%s"') % data['code'])
  232. return qs
  233. def get_context_data(self, **kwargs):
  234. ctx = super().get_context_data(**kwargs)
  235. ctx['voucher_set'] = self.voucher_set
  236. ctx['form'] = self.form
  237. ctx['search_filters'] = self.search_filters
  238. return ctx
  239. class VoucherSetListView(generic.ListView):
  240. model = VoucherSet
  241. context_object_name = 'voucher_sets'
  242. template_name = 'oscar/dashboard/vouchers/voucher_set_list.html'
  243. paginate_by = settings.OSCAR_DASHBOARD_ITEMS_PER_PAGE
  244. def get_queryset(self):
  245. qs = self.model.objects.all().order_by('-date_created')
  246. qs = sort_queryset(
  247. qs, self.request,
  248. ['num_basket_additions', 'num_orders', 'date_created'], '-date_created')
  249. return qs
  250. def get_context_data(self, **kwargs):
  251. ctx = super().get_context_data(**kwargs)
  252. description = _("Voucher sets")
  253. ctx['description'] = description
  254. return ctx
  255. class VoucherSetDownloadView(generic.DetailView):
  256. template_name = 'oscar/dashboard/vouchers/voucher_set_form.html'
  257. model = VoucherSet
  258. form_class = VoucherSetForm
  259. def get(self, request, *args, **kwargs):
  260. voucher_set = self.get_object()
  261. response = HttpResponse(content_type='text/csv')
  262. response['Content-Disposition'] = (
  263. 'attachment; filename="%s.csv"' % slugify(voucher_set.name))
  264. writer = csv.writer(response)
  265. for code in voucher_set.vouchers.values_list('code', flat=True):
  266. writer.writerow([code])
  267. return response
  268. class VoucherSetDeleteView(generic.DeleteView):
  269. model = VoucherSet
  270. template_name = 'oscar/dashboard/vouchers/voucher_set_delete.html'
  271. context_object_name = 'voucher_set'
  272. def get_success_url(self):
  273. messages.warning(self.request, _("Voucher set deleted"))
  274. return reverse('dashboard:voucher-set-list')