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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. from urlparse import urlparse
  2. from django.contrib import messages
  3. from django.core.urlresolvers import reverse, resolve
  4. from django.db.models import get_model
  5. from django.http import HttpResponseRedirect, Http404
  6. from django.views.generic import FormView, View
  7. from django.utils.translation import ugettext_lazy as _
  8. from django.core.exceptions import ObjectDoesNotExist
  9. from extra_views import ModelFormSetView
  10. from oscar.apps.basket.signals import basket_addition, voucher_addition
  11. from oscar.core.loading import get_class, get_classes
  12. Applicator = get_class('offer.utils', 'Applicator')
  13. BasketLineForm, AddToBasketForm, BasketVoucherForm, \
  14. SavedLineFormSet, SavedLineForm, ProductSelectionForm = get_classes(
  15. 'basket.forms', ('BasketLineForm', 'AddToBasketForm',
  16. 'BasketVoucherForm', 'SavedLineFormSet',
  17. 'SavedLineForm', 'ProductSelectionForm'))
  18. Repository = get_class('shipping.repository', ('Repository'))
  19. class BasketView(ModelFormSetView):
  20. model = get_model('basket', 'Line')
  21. basket_model = get_model('basket', 'Basket')
  22. form_class = BasketLineForm
  23. extra = 0
  24. can_delete = True
  25. template_name = 'basket/basket.html'
  26. def get_queryset(self):
  27. return self.request.basket.all_lines()
  28. def get_default_shipping_method(self, basket):
  29. return Repository().get_default_shipping_method(
  30. self.request.user, self.request.basket)
  31. def get_basket_warnings(self, basket):
  32. """
  33. Return a list of warnings that apply to this basket
  34. """
  35. warnings = []
  36. for line in basket.all_lines():
  37. warning = line.get_warning()
  38. if warning:
  39. warnings.append(warning)
  40. return warnings
  41. def get_upsell_messages(self, basket):
  42. offers = Applicator().get_offers(self.request, basket)
  43. msgs = []
  44. for offer in offers:
  45. if offer.is_condition_partially_satisfied(basket):
  46. data = {
  47. 'message': offer.get_upsell_message(basket),
  48. 'offer': offer
  49. }
  50. msgs.append(data)
  51. return msgs
  52. def get_context_data(self, **kwargs):
  53. context = super(BasketView, self).get_context_data(**kwargs)
  54. context['voucher_form'] = BasketVoucherForm()
  55. method = self.get_default_shipping_method(self.request.basket)
  56. context['shipping_method'] = method
  57. context['shipping_charge_incl_tax'] = method.basket_charge_incl_tax()
  58. context['order_total_incl_tax'] = (
  59. self.request.basket.total_incl_tax +
  60. method.basket_charge_incl_tax())
  61. context['basket_warnings'] = self.get_basket_warnings(
  62. self.request.basket)
  63. context['upsell_messages'] = self.get_upsell_messages(
  64. self.request.basket)
  65. if self.request.user.is_authenticated():
  66. try:
  67. saved_basket = self.basket_model.saved.get(
  68. owner=self.request.user)
  69. except self.basket_model.DoesNotExist:
  70. pass
  71. else:
  72. if not saved_basket.is_empty:
  73. saved_queryset = saved_basket.all_lines().select_related(
  74. 'product', 'product__stockrecord')
  75. formset = SavedLineFormSet(user=self.request.user,
  76. basket=self.request.basket,
  77. queryset=saved_queryset,
  78. prefix='saved')
  79. context['saved_formset'] = formset
  80. return context
  81. def get_success_url(self):
  82. messages.success(self.request, _("Basket updated"))
  83. return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
  84. def formset_valid(self, formset):
  85. save_for_later = False
  86. for form in formset:
  87. if (hasattr(form, 'cleaned_data') and
  88. form.cleaned_data['save_for_later']):
  89. line = form.instance
  90. if self.request.user.is_authenticated():
  91. self.move_line_to_saved_basket(line)
  92. messages.info(
  93. self.request,
  94. _(u"'%(title)s' has been saved for later") % {
  95. 'title': line.product})
  96. save_for_later = True
  97. else:
  98. messages.error(
  99. self.request,
  100. _("You can't save an item for later if you're not logged in!"))
  101. return HttpResponseRedirect(self.get_success_url())
  102. if save_for_later:
  103. # No need to call super if we're moving lines to the saved basket
  104. return HttpResponseRedirect(self.get_success_url())
  105. return super(BasketView, self).formset_valid(formset)
  106. def move_line_to_saved_basket(self, line):
  107. saved_basket, _ = get_model('basket', 'basket').saved.get_or_create(
  108. owner=self.request.user)
  109. saved_basket.merge_line(line)
  110. def formset_invalid(self, formset):
  111. errors = []
  112. for error_dict in formset.errors:
  113. errors += [error_list.as_text()
  114. for error_list in error_dict.values()]
  115. msg = _("Your basket couldn't be updated because:\n%s") % (
  116. "\n".join(errors),)
  117. messages.warning(self.request, msg)
  118. return super(BasketView, self).formset_invalid(formset)
  119. class BasketAddView(FormView):
  120. """
  121. Handles the add-to-basket operation, shouldn't be accessed via
  122. GET because there's nothing sensible to render.
  123. """
  124. form_class = AddToBasketForm
  125. product_select_form_class = ProductSelectionForm
  126. product_model = get_model('catalogue', 'product')
  127. add_signal = basket_addition
  128. def get(self, request, *args, **kwargs):
  129. return HttpResponseRedirect(reverse('basket:summary'))
  130. def get_form_kwargs(self):
  131. kwargs = super(BasketAddView, self).get_form_kwargs()
  132. product_select_form = self.product_select_form_class(self.request.POST)
  133. if product_select_form.is_valid():
  134. kwargs['instance'] = product_select_form.cleaned_data['product_id']
  135. else:
  136. kwargs['instance'] = None
  137. kwargs['user'] = self.request.user
  138. kwargs['basket'] = self.request.basket
  139. return kwargs
  140. def get_success_url(self):
  141. url = None
  142. if self.request.POST.get('next'):
  143. url = self.request.POST.get('next')
  144. elif 'HTTP_REFERER' in self.request.META:
  145. url = self.request.META['HTTP_REFERER']
  146. if url:
  147. # We only allow internal URLs so we see if the url resolves
  148. try:
  149. resolve(urlparse(url).path)
  150. except Http404:
  151. url = None
  152. if url is None:
  153. url = reverse('basket:summary')
  154. return url
  155. def form_valid(self, form):
  156. self.request.basket.add_product(
  157. form.instance, form.cleaned_data['quantity'],
  158. form.cleaned_options())
  159. messages.success(self.request, self.get_success_message(form))
  160. # Send signal for basket addition
  161. self.add_signal.send(
  162. sender=self, product=form.instance, user=self.request.user)
  163. return super(BasketAddView, self).form_valid(form)
  164. def get_success_message(self, form):
  165. qty = form.cleaned_data['quantity']
  166. title = form.instance.get_title()
  167. if qty == 1:
  168. return _("'%(title)s' has been added to your basket") % {
  169. 'title': title}
  170. else:
  171. return _(
  172. "'%(title)s' (quantity %(quantity)d) has been added to your"
  173. "basket") % {'title': title, 'quantity': qty}
  174. def form_invalid(self, form):
  175. msgs = []
  176. for error in form.errors.values():
  177. msgs.append(error.as_text())
  178. messages.error(self.request, ",".join(msgs))
  179. return HttpResponseRedirect(
  180. self.request.META.get('HTTP_REFERER', reverse('basket:summary')))
  181. class VoucherAddView(FormView):
  182. form_class = BasketVoucherForm
  183. voucher_model = get_model('voucher', 'voucher')
  184. add_signal = voucher_addition
  185. def get(self, request, *args, **kwargs):
  186. return HttpResponseRedirect(reverse('basket:summary'))
  187. def apply_voucher_to_basket(self, voucher):
  188. if not voucher.is_active():
  189. messages.error(
  190. self.request,
  191. _("The '%(code)s' voucher has expired") % {
  192. 'code': voucher.code})
  193. return
  194. is_available, message = voucher.is_available_to_user(self.request.user)
  195. if not is_available:
  196. messages.error(self.request, message)
  197. return
  198. self.request.basket.vouchers.add(voucher)
  199. # Raise signal
  200. self.add_signal.send(sender=self,
  201. basket=self.request.basket,
  202. voucher=voucher)
  203. # Recalculate discounts to see if the voucher gives any
  204. self.request.basket.remove_discounts()
  205. Applicator().apply(self.request, self.request.basket)
  206. discounts_after = self.request.basket.get_discounts()
  207. # Look for discounts from this new voucher
  208. found_discount = False
  209. for discount in discounts_after:
  210. if discount['voucher'] and discount['voucher'] == voucher:
  211. found_discount = True
  212. break
  213. if not found_discount:
  214. messages.warning(
  215. self.request,
  216. _("Your basket does not qualify for a voucher discount"))
  217. self.request.basket.vouchers.remove(voucher)
  218. else:
  219. messages.info(
  220. self.request,
  221. _("Voucher '%(code)s' added to basket") % {
  222. 'code': voucher.code})
  223. def form_valid(self, form):
  224. code = form.cleaned_data['code']
  225. if not self.request.basket.id:
  226. return HttpResponseRedirect(
  227. self.request.META.get('HTTP_REFERER',
  228. reverse('basket:summary')))
  229. if self.request.basket.contains_voucher(code):
  230. messages.error(
  231. self.request,
  232. _("You have already added the '%(code)s' voucher to "
  233. "your basket") % {'code': code})
  234. else:
  235. try:
  236. voucher = self.voucher_model._default_manager.get(code=code)
  237. except self.voucher_model.DoesNotExist:
  238. messages.error(
  239. self.request,
  240. _("No voucher found with code '%(code)s'") % {
  241. 'code': code})
  242. else:
  243. self.apply_voucher_to_basket(voucher)
  244. return HttpResponseRedirect(
  245. self.request.META.get('HTTP_REFERER', reverse('basket:summary')))
  246. def form_invalid(self, form):
  247. messages.error(self.request, _("Please enter a voucher code"))
  248. return HttpResponseRedirect(reverse('basket:summary') + '#voucher')
  249. class VoucherRemoveView(View):
  250. voucher_model = get_model('voucher', 'voucher')
  251. def get(self, request, *args, **kwargs):
  252. return HttpResponseRedirect(reverse('basket:summary'))
  253. def post(self, request, *args, **kwargs):
  254. voucher_id = int(kwargs.pop('pk'))
  255. if not request.basket.id:
  256. # Hacking attempt - the basket must be saved for it to have
  257. # a voucher in it.
  258. return HttpResponseRedirect(reverse('basket:summary'))
  259. try:
  260. voucher = request.basket.vouchers.get(id=voucher_id)
  261. except ObjectDoesNotExist:
  262. messages.error(
  263. request, _("No voucher found with id '%d'") % voucher_id)
  264. else:
  265. request.basket.vouchers.remove(voucher)
  266. request.basket.save()
  267. messages.info(
  268. request, _("Voucher '%s' removed from basket") % voucher.code)
  269. return HttpResponseRedirect(reverse('basket:summary'))
  270. class SavedView(ModelFormSetView):
  271. model = get_model('basket', 'line')
  272. basket_model = get_model('basket', 'basket')
  273. formset_class = SavedLineFormSet
  274. form_class = SavedLineForm
  275. extra = 0
  276. can_delete = True
  277. def get(self, request, *args, **kwargs):
  278. return HttpResponseRedirect(reverse('basket:summary'))
  279. def get_queryset(self):
  280. try:
  281. saved_basket = self.basket_model.saved.get(owner=self.request.user)
  282. return saved_basket.all_lines().select_related(
  283. 'product', 'product__stockrecord')
  284. except self.basket_model.DoesNotExist:
  285. return []
  286. def get_success_url(self):
  287. return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
  288. def get_formset_kwargs(self):
  289. kwargs = super(SavedView, self).get_formset_kwargs()
  290. kwargs['prefix'] = 'saved'
  291. kwargs['basket'] = self.request.basket
  292. kwargs['user'] = self.request.user
  293. return kwargs
  294. def formset_valid(self, formset):
  295. is_move = False
  296. for form in formset:
  297. if form.cleaned_data['move_to_basket']:
  298. is_move = True
  299. msg = _("'%(product)s' has been moved back to your basket") % {
  300. 'product': form.instance.product}
  301. messages.info(self.request, msg)
  302. real_basket = self.request.basket
  303. real_basket.merge_line(form.instance)
  304. if is_move:
  305. return HttpResponseRedirect(self.get_success_url())
  306. return super(SavedView, self).formset_valid(formset)
  307. def formset_invalid(self, formset):
  308. messages.error(
  309. self.request,
  310. '\n'.join(
  311. error for ed in formset.errors for el
  312. in ed.values() for error in el))
  313. return HttpResponseRedirect(
  314. self.request.META.get('HTTP_REFERER', reverse('basket:summary')))