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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. from django.utils import six
  2. import logging
  3. from django import http
  4. from django.shortcuts import redirect
  5. from django.contrib import messages
  6. from django.contrib.auth import login
  7. from django.core.urlresolvers import reverse, reverse_lazy
  8. from django.utils.translation import ugettext as _
  9. from django.views import generic
  10. from oscar.apps.shipping.methods import NoShippingRequired
  11. from oscar.core.loading import get_class, get_classes, get_model
  12. from . import signals
  13. ShippingAddressForm, GatewayForm \
  14. = get_classes('checkout.forms', ['ShippingAddressForm', 'GatewayForm'])
  15. OrderCreator = get_class('order.utils', 'OrderCreator')
  16. UserAddressForm = get_class('address.forms', 'UserAddressForm')
  17. Repository = get_class('shipping.repository', 'Repository')
  18. AccountAuthView = get_class('customer.views', 'AccountAuthView')
  19. RedirectRequired, UnableToTakePayment, PaymentError \
  20. = get_classes('payment.exceptions', ['RedirectRequired',
  21. 'UnableToTakePayment',
  22. 'PaymentError'])
  23. UnableToPlaceOrder = get_class('order.exceptions', 'UnableToPlaceOrder')
  24. OrderPlacementMixin = get_class('checkout.mixins', 'OrderPlacementMixin')
  25. CheckoutSessionMixin = get_class('checkout.session', 'CheckoutSessionMixin')
  26. Order = get_model('order', 'Order')
  27. ShippingAddress = get_model('order', 'ShippingAddress')
  28. CommunicationEvent = get_model('order', 'CommunicationEvent')
  29. PaymentEventType = get_model('order', 'PaymentEventType')
  30. PaymentEvent = get_model('order', 'PaymentEvent')
  31. UserAddress = get_model('address', 'UserAddress')
  32. Basket = get_model('basket', 'Basket')
  33. Email = get_model('customer', 'Email')
  34. CommunicationEventType = get_model('customer', 'CommunicationEventType')
  35. # Standard logger for checkout events
  36. logger = logging.getLogger('oscar.checkout')
  37. class IndexView(CheckoutSessionMixin, generic.FormView):
  38. """
  39. First page of the checkout. We prompt user to either sign in, or
  40. to proceed as a guest (where we still collect their email address).
  41. """
  42. template_name = 'checkout/gateway.html'
  43. form_class = GatewayForm
  44. success_url = reverse_lazy('checkout:shipping-address')
  45. pre_conditions = [
  46. 'check_basket_is_not_empty',
  47. 'check_basket_is_valid']
  48. def get(self, request, *args, **kwargs):
  49. # We redirect immediately to shipping address stage if the user is
  50. # signed in.
  51. if request.user.is_authenticated():
  52. # We raise a signal to indicate that the user has entered the
  53. # checkout process so analytics tools can track this event.
  54. signals.start_checkout.send_robust(
  55. sender=self, request=request)
  56. return self.get_success_response()
  57. return super(IndexView, self).get(request, *args, **kwargs)
  58. def get_form_kwargs(self):
  59. kwargs = super(IndexView, self).get_form_kwargs()
  60. email = self.checkout_session.get_guest_email()
  61. if email:
  62. kwargs['initial'] = {
  63. 'username': email,
  64. }
  65. return kwargs
  66. def form_valid(self, form):
  67. if form.is_guest_checkout() or form.is_new_account_checkout():
  68. email = form.cleaned_data['username']
  69. self.checkout_session.set_guest_email(email)
  70. # We raise a signal to indicate that the user has entered the
  71. # checkout process by specifying an email address.
  72. signals.start_checkout.send_robust(
  73. sender=self, request=self.request, email=email)
  74. if form.is_new_account_checkout():
  75. messages.info(
  76. self.request,
  77. _("Create your account and then you will be redirected "
  78. "back to the checkout process"))
  79. self.success_url = "%s?next=%s&email=%s" % (
  80. reverse('customer:register'),
  81. reverse('checkout:shipping-address'),
  82. email
  83. )
  84. else:
  85. user = form.get_user()
  86. login(self.request, user)
  87. # We raise a signal to indicate that the user has entered the
  88. # checkout process.
  89. signals.start_checkout.send_robust(
  90. sender=self, request=self.request)
  91. return redirect(self.get_success_url())
  92. def get_success_response(self):
  93. return redirect(self.get_success_url())
  94. # ================
  95. # SHIPPING ADDRESS
  96. # ================
  97. class ShippingAddressView(CheckoutSessionMixin, generic.FormView):
  98. """
  99. Determine the shipping address for the order.
  100. The default behaviour is to display a list of addresses from the users's
  101. address book, from which the user can choose one to be their shipping
  102. address. They can add/edit/delete these USER addresses. This address will
  103. be automatically converted into a SHIPPING address when the user checks
  104. out.
  105. Alternatively, the user can enter a SHIPPING address directly which will be
  106. saved in the session and later saved as ShippingAddress model when the
  107. order is successfully submitted.
  108. """
  109. template_name = 'checkout/shipping_address.html'
  110. form_class = ShippingAddressForm
  111. success_url = reverse_lazy('checkout:shipping-method')
  112. pre_conditions = ['check_basket_is_not_empty',
  113. 'check_basket_is_valid',
  114. 'check_user_email_is_captured']
  115. skip_conditions = ['skip_unless_basket_requires_shipping']
  116. def get_initial(self):
  117. return self.checkout_session.new_shipping_address_fields()
  118. def get_context_data(self, **kwargs):
  119. ctx = super(ShippingAddressView, self).get_context_data(**kwargs)
  120. if self.request.user.is_authenticated():
  121. # Look up address book data
  122. ctx['addresses'] = self.get_available_addresses()
  123. return ctx
  124. def get_available_addresses(self):
  125. # Include only addresses where the country is flagged as valid for
  126. # shipping. Also, use ordering to ensure the default address comes
  127. # first.
  128. return self.request.user.addresses.filter(
  129. country__is_shipping_country=True).order_by(
  130. '-is_default_for_shipping')
  131. def post(self, request, *args, **kwargs):
  132. # Check if a shipping address was selected directly (eg no form was
  133. # filled in)
  134. if self.request.user.is_authenticated() \
  135. and 'address_id' in self.request.POST:
  136. address = UserAddress._default_manager.get(
  137. pk=self.request.POST['address_id'], user=self.request.user)
  138. action = self.request.POST.get('action', None)
  139. if action == 'ship_to':
  140. # User has selected a previous address to ship to
  141. self.checkout_session.ship_to_user_address(address)
  142. return redirect(self.get_success_url())
  143. elif action == 'delete':
  144. # Delete the selected address
  145. address.delete()
  146. messages.info(self.request, _("Address deleted from your"
  147. " address book"))
  148. return redirect(self.get_success_url())
  149. else:
  150. return http.HttpResponseBadRequest()
  151. else:
  152. return super(ShippingAddressView, self).post(
  153. request, *args, **kwargs)
  154. def form_valid(self, form):
  155. # Store the address details in the session and redirect to next step
  156. address_fields = dict(
  157. (k, v) for (k, v) in form.instance.__dict__.items()
  158. if not k.startswith('_'))
  159. self.checkout_session.ship_to_new_address(address_fields)
  160. return super(ShippingAddressView, self).form_valid(form)
  161. class UserAddressUpdateView(CheckoutSessionMixin, generic.UpdateView):
  162. """
  163. Update a user address
  164. """
  165. template_name = 'checkout/user_address_form.html'
  166. form_class = UserAddressForm
  167. success_url = reverse_lazy('checkout:shipping-address')
  168. def get_queryset(self):
  169. return self.request.user.addresses.all()
  170. def get_form_kwargs(self):
  171. kwargs = super(UserAddressUpdateView, self).get_form_kwargs()
  172. kwargs['user'] = self.request.user
  173. return kwargs
  174. def get_success_url(self):
  175. messages.info(self.request, _("Address saved"))
  176. return super(UserAddressUpdateView, self).get_success_url()
  177. class UserAddressDeleteView(CheckoutSessionMixin, generic.DeleteView):
  178. """
  179. Delete an address from a user's addressbook.
  180. """
  181. template_name = 'checkout/user_address_delete.html'
  182. success_url = reverse_lazy('checkout:shipping-address')
  183. def get_queryset(self):
  184. return self.request.user.addresses.all()
  185. def get_success_url(self):
  186. messages.info(self.request, _("Address deleted"))
  187. return super(UserAddressDeleteView, self).get_success_url()
  188. # ===============
  189. # Shipping method
  190. # ===============
  191. class ShippingMethodView(CheckoutSessionMixin, generic.TemplateView):
  192. """
  193. View for allowing a user to choose a shipping method.
  194. Shipping methods are largely domain-specific and so this view
  195. will commonly need to be subclassed and customised.
  196. The default behaviour is to load all the available shipping methods
  197. using the shipping Repository. If there is only 1, then it is
  198. automatically selected. Otherwise, a page is rendered where
  199. the user can choose the appropriate one.
  200. """
  201. template_name = 'checkout/shipping_methods.html'
  202. pre_conditions = ['check_basket_is_not_empty',
  203. 'check_basket_is_valid',
  204. 'check_user_email_is_captured']
  205. def get(self, request, *args, **kwargs):
  206. # These pre-conditions can't easily be factored out into the normal
  207. # pre-conditions as they do more than run a test and then raise an
  208. # exception on failure.
  209. # Check that shipping is required at all
  210. if not request.basket.is_shipping_required():
  211. # No shipping required - we store a special code to indicate so.
  212. self.checkout_session.use_shipping_method(
  213. NoShippingRequired().code)
  214. return self.get_success_response()
  215. # Check that shipping address has been completed
  216. if not self.checkout_session.is_shipping_address_set():
  217. messages.error(request, _("Please choose a shipping address"))
  218. return redirect('checkout:shipping-address')
  219. # Save shipping methods as instance var as we need them both here
  220. # and when setting the context vars.
  221. self._methods = self.get_available_shipping_methods()
  222. if len(self._methods) == 0:
  223. # No shipping methods available for given address
  224. messages.warning(request, _(
  225. "Shipping is unavailable for your chosen address - please "
  226. "choose another"))
  227. return redirect('checkout:shipping-address')
  228. elif len(self._methods) == 1:
  229. # Only one shipping method - set this and redirect onto the next
  230. # step
  231. self.checkout_session.use_shipping_method(self._methods[0].code)
  232. return self.get_success_response()
  233. # Must be more than one available shipping method, we present them to
  234. # the user to make a choice.
  235. return super(ShippingMethodView, self).get(request, *args, **kwargs)
  236. def get_context_data(self, **kwargs):
  237. kwargs = super(ShippingMethodView, self).get_context_data(**kwargs)
  238. kwargs['methods'] = self._methods
  239. return kwargs
  240. def get_available_shipping_methods(self):
  241. """
  242. Returns all applicable shipping method objects for a given basket.
  243. """
  244. # Shipping methods can depend on the user, the contents of the basket
  245. # and the shipping address (so we pass all these things to the
  246. # repository). I haven't come across a scenario that doesn't fit this
  247. # system.
  248. return Repository().get_shipping_methods(
  249. basket=self.request.basket, user=self.request.user,
  250. shipping_addr=self.get_shipping_address(self.request.basket),
  251. request=self.request)
  252. def is_valid_shipping_method(self, method_code):
  253. for method in self.get_available_shipping_methods():
  254. if method.code == method_code:
  255. return True
  256. return False
  257. def post(self, request, *args, **kwargs):
  258. # Need to check that this code is valid for this user
  259. method_code = request.POST.get('method_code', None)
  260. if not self.is_valid_shipping_method(method_code):
  261. messages.error(request, _("Your submitted shipping method is not"
  262. " permitted"))
  263. return redirect('checkout:shipping-method')
  264. # Save the code for the chosen shipping method in the session
  265. # and continue to the next step.
  266. self.checkout_session.use_shipping_method(method_code)
  267. return self.get_success_response()
  268. def get_success_response(self):
  269. return redirect('checkout:payment-method')
  270. # ==============
  271. # Payment method
  272. # ==============
  273. class PaymentMethodView(CheckoutSessionMixin, generic.TemplateView):
  274. """
  275. View for a user to choose which payment method(s) they want to use.
  276. This would include setting allocations if payment is to be split
  277. between multiple sources. It's not the place for entering sensitive details
  278. like bankcard numbers though - that belongs on the payment details view.
  279. """
  280. pre_conditions = [
  281. 'check_basket_is_not_empty',
  282. 'check_basket_is_valid',
  283. 'check_user_email_is_captured',
  284. 'check_shipping_data_is_captured']
  285. skip_conditions = ['skip_unless_payment_is_required']
  286. def get(self, request, *args, **kwargs):
  287. # By default we redirect straight onto the payment details view. Shops
  288. # that require a choice of payment method may want to override this
  289. # method to implement their specific logic.
  290. return self.get_success_response()
  291. def get_success_response(self):
  292. return redirect('checkout:payment-details')
  293. # ================
  294. # Order submission
  295. # ================
  296. class PaymentDetailsView(OrderPlacementMixin, generic.TemplateView):
  297. """
  298. For taking the details of payment and creating the order.
  299. This view class is used by two separate URLs: 'payment-details' and
  300. 'preview'. The `preview` class attribute is used to distinguish which is
  301. being used. Chronologically, `payment-details` (preview=False) comes before
  302. `preview` (preview=True).
  303. If sensitive details are required (eg a bankcard), then the payment details
  304. view should submit to the preview URL and a custom implementation of
  305. `validate_payment_submission` should be provided.
  306. - If the form data is valid, then the preview template can be rendered with
  307. the payment-details forms re-rendered within a hidden div so they can be
  308. re-submitted when the 'place order' button is clicked. This avoids having
  309. to write sensitive data to disk anywhere during the process. This can be
  310. done by calling `render_preview`, passing in the extra template context
  311. vars.
  312. - If the form data is invalid, then the payment details templates needs to
  313. be re-rendered with the relevant error messages. This can be done by
  314. calling `render_payment_details`, passing in the form instances to pass
  315. to the templates.
  316. The class is deliberately split into fine-grained methods, responsible for
  317. only one thing. This is to make it easier to subclass and override just
  318. one component of functionality.
  319. All projects will need to subclass and customise this class as no payment
  320. is taken by default.
  321. """
  322. template_name = 'checkout/payment_details.html'
  323. template_name_preview = 'checkout/preview.html'
  324. # These conditions are extended at runtime depending on whether we are in
  325. # 'preview' mode or not.
  326. pre_conditions = [
  327. 'check_basket_is_not_empty',
  328. 'check_basket_is_valid',
  329. 'check_user_email_is_captured',
  330. 'check_shipping_data_is_captured']
  331. # If preview=True, then we render a preview template that shows all order
  332. # details ready for submission.
  333. preview = False
  334. def get_pre_conditions(self, request):
  335. if self.preview:
  336. # The preview view needs to ensure payment information has been
  337. # correctly captured.
  338. return self.pre_conditions + ['check_payment_data_is_captured']
  339. return super(PaymentDetailsView, self).get_pre_conditions(request)
  340. def get_skip_conditions(self, request):
  341. if not self.preview:
  342. # Payment details should only be collected if necessary
  343. return ['skip_unless_payment_is_required']
  344. return super(PaymentDetailsView, self).get_skip_conditions(request)
  345. def post(self, request, *args, **kwargs):
  346. # Posting to payment-details isn't the right thing to do. Form
  347. # submissions should use the preview URL.
  348. if not self.preview:
  349. return http.HttpResponseBadRequest()
  350. # We use a custom parameter to indicate if this is an attempt to place
  351. # an order (normally from the preview page). Without this, we assume a
  352. # payment form is being submitted from the payment details view. In
  353. # this case, the form needs validating and the order preview shown.
  354. if request.POST.get('action', '') == 'place_order':
  355. return self.handle_place_order_submission(request)
  356. return self.handle_payment_details_submission(request)
  357. def handle_place_order_submission(self, request):
  358. """
  359. Handle a request to place an order.
  360. This method is normally called after the customer has clicked "place
  361. order" on the preview page. It's responsible for (re-)validating any
  362. form information then building the submission dict to pass to the
  363. `submit` method.
  364. If forms are submitted on your payment details view, you should
  365. override this method to ensure they are valid before extracting their
  366. data into the submission dict and passing it onto `submit`.
  367. """
  368. return self.submit(**self.build_submission())
  369. def handle_payment_details_submission(self, request):
  370. """
  371. Handle a request to submit payment details.
  372. This method will need to be overridden by projects that require forms
  373. to be submitted on the payment details view. The new version of this
  374. method should validate the submitted form data and:
  375. - If the form data is valid, show the preview view with the forms
  376. re-rendered in the page
  377. - If the form data is invalid, show the payment details view with
  378. the form errors showing.
  379. """
  380. # No form data to validate by default, so we simply render the preview
  381. # page. If validating form data and it's invalid, then call the
  382. # render_payment_details view.
  383. return self.render_preview(request)
  384. def render_preview(self, request, **kwargs):
  385. """
  386. Show a preview of the order.
  387. If sensitive data was submitted on the payment details page, you will
  388. need to pass it back to the view here so it can be stored in hidden
  389. form inputs. This avoids ever writing the sensitive data to disk.
  390. """
  391. self.preview = True
  392. ctx = self.get_context_data(**kwargs)
  393. return self.render_to_response(ctx)
  394. def render_payment_details(self, request, **kwargs):
  395. """
  396. Show the payment details page
  397. This method is useful if the submission from the payment details view
  398. is invalid and needs to be re-rendered with form errors showing.
  399. """
  400. self.preview = False
  401. ctx = self.get_context_data(**kwargs)
  402. return self.render_to_response(ctx)
  403. def get_default_billing_address(self):
  404. """
  405. Return default billing address for user
  406. This is useful when the payment details view includes a billing address
  407. form - you can use this helper method to prepopulate the form.
  408. Note, this isn't used in core oscar as there is no billing address form
  409. by default.
  410. """
  411. if not self.request.user.is_authenticated():
  412. return None
  413. try:
  414. return self.request.user.addresses.get(is_default_for_billing=True)
  415. except UserAddress.DoesNotExist:
  416. return None
  417. def submit(self, user, basket, shipping_address, shipping_method, # noqa (too complex (10))
  418. shipping_charge, order_total, payment_kwargs=None,
  419. order_kwargs=None):
  420. """
  421. Submit a basket for order placement.
  422. The process runs as follows:
  423. * Generate an order number
  424. * Freeze the basket so it cannot be modified any more (important when
  425. redirecting the user to another site for payment as it prevents the
  426. basket being manipulated during the payment process).
  427. * Attempt to take payment for the order
  428. - If payment is successful, place the order
  429. - If a redirect is required (eg PayPal, 3DSecure), redirect
  430. - If payment is unsuccessful, show an appropriate error message
  431. :basket: The basket to submit.
  432. :payment_kwargs: Additional kwargs to pass to the handle_payment method
  433. :order_kwargs: Additional kwargs to pass to the place_order method
  434. """
  435. if payment_kwargs is None:
  436. payment_kwargs = {}
  437. if order_kwargs is None:
  438. order_kwargs = {}
  439. # Taxes must be known at this point
  440. assert basket.is_tax_known, (
  441. "Basket tax must be set before a user can place an order")
  442. assert shipping_charge.is_tax_known, (
  443. "Shipping charge tax must be set before a user can place an order")
  444. # We generate the order number first as this will be used
  445. # in payment requests (ie before the order model has been
  446. # created). We also save it in the session for multi-stage
  447. # checkouts (eg where we redirect to a 3rd party site and place
  448. # the order on a different request).
  449. order_number = self.generate_order_number(basket)
  450. self.checkout_session.set_order_number(order_number)
  451. logger.info("Order #%s: beginning submission process for basket #%d",
  452. order_number, basket.id)
  453. # Freeze the basket so it cannot be manipulated while the customer is
  454. # completing payment on a 3rd party site. Also, store a reference to
  455. # the basket in the session so that we know which basket to thaw if we
  456. # get an unsuccessful payment response when redirecting to a 3rd party
  457. # site.
  458. self.freeze_basket(basket)
  459. self.checkout_session.set_submitted_basket(basket)
  460. # We define a general error message for when an unanticipated payment
  461. # error occurs.
  462. error_msg = _("A problem occurred while processing payment for this "
  463. "order - no payment has been taken. Please "
  464. "contact customer services if this problem persists")
  465. signals.pre_payment.send_robust(sender=self, view=self)
  466. try:
  467. self.handle_payment(order_number, order_total, **payment_kwargs)
  468. except RedirectRequired as e:
  469. # Redirect required (eg PayPal, 3DS)
  470. logger.info("Order #%s: redirecting to %s", order_number, e.url)
  471. return http.HttpResponseRedirect(e.url)
  472. except UnableToTakePayment as e:
  473. # Something went wrong with payment but in an anticipated way. Eg
  474. # their bankcard has expired, wrong card number - that kind of
  475. # thing. This type of exception is supposed to set a friendly error
  476. # message that makes sense to the customer.
  477. msg = six.text_type(e)
  478. logger.warning(
  479. "Order #%s: unable to take payment (%s) - restoring basket",
  480. order_number, msg)
  481. self.restore_frozen_basket()
  482. # We assume that the details submitted on the payment details view
  483. # were invalid (eg expired bankcard).
  484. return self.render_payment_details(
  485. self.request, error=msg, **payment_kwargs)
  486. except PaymentError as e:
  487. # A general payment error - Something went wrong which wasn't
  488. # anticipated. Eg, the payment gateway is down (it happens), your
  489. # credentials are wrong - that king of thing.
  490. # It makes sense to configure the checkout logger to
  491. # mail admins on an error as this issue warrants some further
  492. # investigation.
  493. msg = six.text_type(e)
  494. logger.error("Order #%s: payment error (%s)", order_number, msg,
  495. exc_info=True)
  496. self.restore_frozen_basket()
  497. return self.render_preview(
  498. self.request, error=error_msg, **payment_kwargs)
  499. except Exception as e:
  500. # Unhandled exception - hopefully, you will only ever see this in
  501. # development...
  502. logger.error(
  503. "Order #%s: unhandled exception while taking payment (%s)",
  504. order_number, e, exc_info=True)
  505. self.restore_frozen_basket()
  506. return self.render_preview(
  507. self.request, error=error_msg, **payment_kwargs)
  508. signals.post_payment.send_robust(sender=self, view=self)
  509. # If all is ok with payment, try and place order
  510. logger.info("Order #%s: payment successful, placing order",
  511. order_number)
  512. try:
  513. return self.handle_order_placement(
  514. order_number, user, basket, shipping_address, shipping_method,
  515. shipping_charge, order_total, **order_kwargs)
  516. except UnableToPlaceOrder as e:
  517. # It's possible that something will go wrong while trying to
  518. # actually place an order. Not a good situation to be in as a
  519. # payment transaction may already have taken place, but needs
  520. # to be handled gracefully.
  521. msg = six.text_type(e)
  522. logger.error("Order #%s: unable to place order - %s",
  523. order_number, msg, exc_info=True)
  524. self.restore_frozen_basket()
  525. return self.render_preview(
  526. self.request, error=msg, **payment_kwargs)
  527. def get_template_names(self):
  528. return [self.template_name_preview] if self.preview else [
  529. self.template_name]
  530. # =========
  531. # Thank you
  532. # =========
  533. class ThankYouView(generic.DetailView):
  534. """
  535. Displays the 'thank you' page which summarises the order just submitted.
  536. """
  537. template_name = 'checkout/thank_you.html'
  538. context_object_name = 'order'
  539. def get_object(self):
  540. # We allow superusers to force an order thank-you page for testing
  541. order = None
  542. if self.request.user.is_superuser:
  543. if 'order_number' in self.request.GET:
  544. order = Order._default_manager.get(
  545. number=self.request.GET['order_number'])
  546. elif 'order_id' in self.request.GET:
  547. order = Order._default_manager.get(
  548. id=self.request.GET['order_id'])
  549. if not order:
  550. if 'checkout_order_id' in self.request.session:
  551. order = Order._default_manager.get(
  552. pk=self.request.session['checkout_order_id'])
  553. else:
  554. raise http.Http404(_("No order found"))
  555. return order