Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import datetime
  2. from django.views.generic import (ListView, FormView, DeleteView, DetailView,
  3. CreateView, UpdateView)
  4. from django.db.models.loading import get_model
  5. from django.core.urlresolvers import reverse
  6. from django.contrib import messages
  7. from django.http import HttpResponseRedirect
  8. from django.shortcuts import get_object_or_404
  9. from django.utils.translation import ugettext_lazy as _
  10. from oscar.core.loading import get_classes, get_class
  11. ConditionalOffer = get_model('offer', 'ConditionalOffer')
  12. Condition = get_model('offer', 'Condition')
  13. Range = get_model('offer', 'Range')
  14. Product = get_model('catalogue', 'Product')
  15. OrderDiscount = get_model('order', 'OrderDiscount')
  16. Benefit = get_model('offer', 'Benefit')
  17. MetaDataForm, ConditionForm, BenefitForm, RestrictionsForm, OfferSearchForm = get_classes(
  18. 'dashboard.offers.forms', [
  19. 'MetaDataForm', 'ConditionForm', 'BenefitForm', 'RestrictionsForm',
  20. 'OfferSearchForm'])
  21. OrderDiscountCSVFormatter = get_class(
  22. 'dashboard.offers.reports', 'OrderDiscountCSVFormatter')
  23. class OfferListView(ListView):
  24. model = ConditionalOffer
  25. context_object_name = 'offers'
  26. template_name = 'dashboard/offers/offer_list.html'
  27. form_class = OfferSearchForm
  28. def get_queryset(self):
  29. qs = self.model._default_manager.filter(
  30. offer_type=ConditionalOffer.SITE)
  31. qs = self.sort_queryset(qs)
  32. self.description = _("All offers")
  33. # We track whether the queryset is filtered to determine whether we
  34. # show the search form 'reset' button.
  35. self.is_filtered = False
  36. self.form = self.form_class(self.request.GET)
  37. if not self.form.is_valid():
  38. return qs
  39. data = self.form.cleaned_data
  40. if data['name']:
  41. qs = qs.filter(name__icontains=data['name'])
  42. self.description = _("Offers matching '%s'") % data['name']
  43. self.is_filtered = True
  44. if data['is_active']:
  45. self.is_filtered = True
  46. today = datetime.date.today()
  47. qs = qs.filter(start_date__lte=today, end_date__gte=today)
  48. return qs
  49. def sort_queryset(self, queryset):
  50. sort = self.request.GET.get('sort', None)
  51. allowed_sorts = ['name', 'start_date', 'end_date', 'num_applications',
  52. 'total_discount']
  53. if sort in allowed_sorts:
  54. direction = self.request.GET.get('dir', 'desc')
  55. sort = ('-' if direction == 'desc' else '') + sort
  56. queryset = queryset.order_by(sort)
  57. return queryset
  58. def get_context_data(self, **kwargs):
  59. ctx = super(OfferListView, self).get_context_data(**kwargs)
  60. ctx['queryset_description'] = self.description
  61. ctx['form'] = self.form
  62. ctx['is_filtered'] = self.is_filtered
  63. return ctx
  64. class OfferWizardStepView(FormView):
  65. wizard_name = 'offer_wizard'
  66. form_class = None
  67. step_name = None
  68. update = False
  69. url_name = None
  70. # Keep a reference to previous view class to allow checks to be made on
  71. # whether prior steps have been completed
  72. previous_view = None
  73. def dispatch(self, request, *args, **kwargs):
  74. if self.update:
  75. self.offer = get_object_or_404(ConditionalOffer, id=kwargs['pk'])
  76. if not self.is_previous_step_complete(request):
  77. messages.warning(
  78. request, _("%s step not complete") % (
  79. self.previous_view.step_name.title(),))
  80. return HttpResponseRedirect(self.get_back_url())
  81. return super(OfferWizardStepView, self).dispatch(request, *args, **kwargs)
  82. def is_previous_step_complete(self, request):
  83. if not self.previous_view:
  84. return True
  85. return self.previous_view.is_valid(self, request)
  86. def _key(self, step_name=None, is_object=False):
  87. key = step_name if step_name else self.step_name
  88. if self.update:
  89. key += str(self.offer.id)
  90. if is_object:
  91. key += '_obj'
  92. return key
  93. def _store_form_kwargs(self, form):
  94. session_data = self.request.session.setdefault(self.wizard_name, {})
  95. form_kwargs = {'data': form.cleaned_data.copy()}
  96. session_data[self._key()] = form_kwargs
  97. self.request.session.save()
  98. def _fetch_form_kwargs(self, step_name=None):
  99. if not step_name:
  100. step_name = self.step_name
  101. session_data = self.request.session.setdefault(self.wizard_name, {})
  102. return session_data.get(self._key(step_name), {})
  103. def _store_object(self, form):
  104. session_data = self.request.session.setdefault(self.wizard_name, {})
  105. session_data[self._key(is_object=True)] = form.save(commit=False)
  106. self.request.session.save()
  107. def _fetch_object(self, step_name, request=None):
  108. if request is None:
  109. request = self.request
  110. session_data = request.session.setdefault(self.wizard_name, {})
  111. return session_data.get(self._key(step_name, is_object=True), None)
  112. def _fetch_session_offer(self):
  113. """
  114. Return the offer instance loaded with the data stored in the
  115. session. When updating an offer, the updated fields are used with the
  116. existing offer data.
  117. """
  118. offer = self._fetch_object('metadata')
  119. if offer is None and self.update:
  120. offer = self.offer
  121. if offer is not None:
  122. condition = self._fetch_object('condition')
  123. if condition:
  124. offer.condition = condition
  125. benefit = self._fetch_object('benefit')
  126. if benefit:
  127. offer.benefit = benefit
  128. return offer
  129. def _flush_session(self):
  130. self.request.session[self.wizard_name] = {}
  131. self.request.session.save()
  132. def get_form_kwargs(self, *args, **kwargs):
  133. form_kwargs = {}
  134. if self.update:
  135. form_kwargs['instance'] = self.get_instance()
  136. session_kwargs = self._fetch_form_kwargs()
  137. form_kwargs.update(session_kwargs)
  138. parent_kwargs = super(OfferWizardStepView, self).get_form_kwargs(
  139. *args, **kwargs)
  140. form_kwargs.update(parent_kwargs)
  141. return form_kwargs
  142. def get_context_data(self, **kwargs):
  143. ctx = super(OfferWizardStepView, self).get_context_data(**kwargs)
  144. if self.update:
  145. ctx['offer'] = self.offer
  146. ctx['session_offer'] = self._fetch_session_offer()
  147. ctx['title'] = self.get_title()
  148. return ctx
  149. def get_back_url(self):
  150. if not self.previous_view:
  151. return None
  152. if self.update:
  153. return reverse(self.previous_view.url_name,
  154. kwargs={'pk': self.kwargs['pk']})
  155. return reverse(self.previous_view.url_name)
  156. def get_title(self):
  157. return self.step_name.title()
  158. def form_valid(self, form):
  159. self._store_form_kwargs(form)
  160. self._store_object(form)
  161. return super(OfferWizardStepView, self).form_valid(form)
  162. def get_success_url(self):
  163. if self.update:
  164. return reverse(self.success_url_name,
  165. kwargs={'pk': self.kwargs['pk']})
  166. return reverse(self.success_url_name)
  167. @classmethod
  168. def is_valid(cls, current_view, request):
  169. if current_view.update:
  170. return True
  171. return current_view._fetch_object(cls.step_name, request) is not None
  172. class OfferMetaDataView(OfferWizardStepView):
  173. step_name = 'metadata'
  174. form_class = MetaDataForm
  175. template_name = 'dashboard/offers/metadata_form.html'
  176. url_name = 'dashboard:offer-metadata'
  177. success_url_name = 'dashboard:offer-benefit'
  178. def get_instance(self):
  179. return self.offer
  180. def get_title(self):
  181. return _("Name and description")
  182. class OfferBenefitView(OfferWizardStepView):
  183. step_name = 'benefit'
  184. form_class = BenefitForm
  185. template_name = 'dashboard/offers/benefit_form.html'
  186. url_name = 'dashboard:offer-benefit'
  187. success_url_name = 'dashboard:offer-condition'
  188. previous_view = OfferMetaDataView
  189. def get_instance(self):
  190. return self.offer.benefit
  191. def get_title(self):
  192. # This is referred to as the 'incentive' within the dashboard.
  193. return _("Incentive")
  194. class OfferConditionView(OfferWizardStepView):
  195. step_name = 'condition'
  196. form_class = ConditionForm
  197. template_name = 'dashboard/offers/condition_form.html'
  198. url_name = 'dashboard:offer-condition'
  199. success_url_name = 'dashboard:offer-restrictions'
  200. previous_view = OfferBenefitView
  201. def get_instance(self):
  202. return self.offer.condition
  203. class OfferRestrictionsView(OfferWizardStepView):
  204. step_name = 'restrictions'
  205. form_class = RestrictionsForm
  206. template_name = 'dashboard/offers/restrictions_form.html'
  207. previous_view = OfferConditionView
  208. url_name = 'dashboard:offer-restrictions'
  209. def form_valid(self, form):
  210. offer = form.save(commit=False)
  211. # We update the offer with the name/description from step 1
  212. session_offer = self._fetch_session_offer()
  213. offer.name = session_offer.name
  214. offer.description = session_offer.description
  215. # Working around a strange Django issue where saving the related model
  216. # in place does not register it correctly and so it has to be saved and
  217. # reassigned.
  218. benefit = session_offer.benefit
  219. benefit.save()
  220. condition = session_offer.condition
  221. condition.save()
  222. offer.benefit = benefit
  223. offer.condition = condition
  224. offer.save()
  225. self._flush_session()
  226. if self.update:
  227. msg = _("Offer '%s' updated") % offer.name
  228. else:
  229. msg = _("Offer '%s' created!") % offer.name
  230. messages.success(self.request, msg)
  231. return HttpResponseRedirect(reverse(
  232. 'dashboard:offer-detail', kwargs={'pk': offer.pk}))
  233. def get_instance(self):
  234. return self.offer
  235. def get_title(self):
  236. return _("Restrictions")
  237. class OfferDeleteView(DeleteView):
  238. model = ConditionalOffer
  239. template_name = 'dashboard/offers/offer_delete.html'
  240. context_object_name = 'offer'
  241. def get_success_url(self):
  242. messages.success(self.request, _("Offer deleted!"))
  243. return reverse('dashboard:offer-list')
  244. class OfferDetailView(ListView):
  245. # Slightly odd, but we treat the offer detail view as a list view so the
  246. # order discounts can be browsed.
  247. model = OrderDiscount
  248. template_name = 'dashboard/offers/offer_detail.html'
  249. context_object_name = 'order_discounts'
  250. def dispatch(self, request, *args, **kwargs):
  251. self.offer = get_object_or_404(ConditionalOffer, pk=kwargs['pk'])
  252. return super(OfferDetailView, self).dispatch(request, *args, **kwargs)
  253. def post(self, request, *args, **kwargs):
  254. if 'suspend' in request.POST:
  255. return self.suspend()
  256. elif 'unsuspend' in request.POST:
  257. return self.unsuspend()
  258. def suspend(self):
  259. if self.offer.is_suspended:
  260. messages.error(self.request, _("Offer is already suspended"))
  261. else:
  262. self.offer.suspend()
  263. messages.success(self.request, _("Offer suspended"))
  264. return HttpResponseRedirect(
  265. reverse('dashboard:offer-detail', kwargs={'pk': self.offer.pk}))
  266. def unsuspend(self):
  267. if not self.offer.is_suspended:
  268. messages.error(
  269. self.request,
  270. _("Offer cannot be reinstated as it is not currently "
  271. "suspended"))
  272. else:
  273. self.offer.unsuspend()
  274. messages.success(self.request, _("Offer reinstated"))
  275. return HttpResponseRedirect(
  276. reverse('dashboard:offer-detail', kwargs={'pk': self.offer.pk}))
  277. def get_queryset(self):
  278. return self.model.objects.filter(offer_id=self.offer.pk)
  279. def get_context_data(self, **kwargs):
  280. ctx = super(OfferDetailView, self).get_context_data(**kwargs)
  281. ctx['offer'] = self.offer
  282. return ctx
  283. def render_to_response(self, context):
  284. if self.request.GET.get('format') == 'csv':
  285. formatter = OrderDiscountCSVFormatter()
  286. return formatter.generate_response(context['order_discounts'],
  287. offer=self.offer)
  288. return super(OfferDetailView, self).render_to_response(context)
  289. class RangeListView(ListView):
  290. model = Range
  291. context_object_name = 'ranges'
  292. template_name = 'dashboard/offers/range_list.html'
  293. class RangeCreateView(CreateView):
  294. model = Range
  295. template_name = 'dashboard/offers/range_form.html'
  296. def get_success_url(self):
  297. messages.success(self.request, _("Range created"))
  298. return reverse('dashboard:range-list')
  299. class RangeUpdateView(UpdateView):
  300. model = Range
  301. template_name = 'dashboard/offers/range_form.html'
  302. def get_success_url(self):
  303. messages.success(self.request, _("Range updated"))
  304. return reverse('dashboard:range-list')
  305. class RangeDeleteView(DeleteView):
  306. model = Range
  307. template_name = 'dashboard/offers/range_delete.html'
  308. context_object_name = 'range'
  309. def get_success_url(self):
  310. messages.warning(self.request, _("Range deleted"))
  311. return reverse('dashboard:range-list')
  312. class RangeProductListView(ListView):
  313. model = Product
  314. template_name = 'dashboard/offers/range_product_list.html'
  315. context_object_name = 'products'
  316. def get(self, request, *args, **kwargs):
  317. self.range = get_object_or_404(Range, id=self.kwargs['pk'])
  318. return super(RangeProductListView, self).get(request, *args, **kwargs)
  319. def get_queryset(self):
  320. return self.range.included_products.all()
  321. def get_context_data(self, **kwargs):
  322. ctx = super(RangeProductListView, self).get_context_data(**kwargs)
  323. ctx['range'] = self.range
  324. return ctx