| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import logging
-
- from django.http import HttpResponseRedirect
- from django.core.urlresolvers import reverse, NoReverseMatch
- from django.contrib.sites.models import Site, get_current_site
- from django.core.exceptions import ObjectDoesNotExist
- from django.db.models import get_model
-
- from oscar.core.loading import get_class
- from oscar.core.decorators import deprecated
-
- OrderCreator = get_class('order.utils', 'OrderCreator')
- Dispatcher = get_class('customer.utils', 'Dispatcher')
- CheckoutSessionMixin = get_class('checkout.session', 'CheckoutSessionMixin')
- ShippingAddress = get_model('order', 'ShippingAddress')
- CommunicationEvent = get_model('order', 'CommunicationEvent')
- PaymentEventType = get_model('order', 'PaymentEventType')
- PaymentEvent = get_model('order', 'PaymentEvent')
- PaymentEventQuantity = get_model('order', 'PaymentEventQuantity')
- UserAddress = get_model('address', 'UserAddress')
- Basket = get_model('basket', 'Basket')
- CommunicationEventType = get_model('customer', 'CommunicationEventType')
- UnableToPlaceOrder = get_class('order.exceptions', 'UnableToPlaceOrder')
-
- post_checkout = get_class('checkout.signals', 'post_checkout')
-
- # Standard logger for checkout events
- logger = logging.getLogger('oscar.checkout')
-
-
- class OrderPlacementMixin(CheckoutSessionMixin):
- """
- Mixin which provides functionality for placing orders.
- """
- # Any payment sources should be added to this list as part of the
- # _handle_payment method. If the order is placed successfully, then
- # they will be persisted.
- _payment_sources = None
-
- _payment_events = None
-
- # Default code for the email to send after successful checkout
- communication_type_code = 'ORDER_PLACED'
- view_signal = post_checkout
-
- def handle_order_placement(self, order_number, user, basket,
- shipping_address, shipping_method,
- total, **kwargs):
- """
- Write out the order models and return the appropriate HTTP response
-
- We deliberately pass the basket in here as the one tied to the request
- isn't necessarily the correct one to use in placing the order. This
- can happen when a basket gets frozen.
- """
- order = self.place_order(
- order_number, user, basket, shipping_address, shipping_method,
- total, **kwargs)
- basket.submit()
- return self.handle_successful_order(order)
-
- def place_order(self, order_number, user, basket, shipping_address,
- shipping_method, total, billing_address=None, **kwargs):
- """
- Writes the order out to the DB including the payment models
- """
- # Create saved shipping address instance from passed in unsaved
- # instance
- shipping_address = self.create_shipping_address(user, shipping_address)
-
- # We pass the kwargs as they often include the billing address form
- # which will be needed to save a billing address.
- billing_address = self.create_billing_address(
- billing_address, shipping_address, **kwargs)
-
- if 'status' not in kwargs:
- status = self.get_initial_order_status(basket)
- else:
- status = kwargs.pop('status')
-
- order = OrderCreator().place_order(
- user=user,
- order_number=order_number,
- basket=basket,
- shipping_address=shipping_address,
- shipping_method=shipping_method,
- total=total,
- billing_address=billing_address,
- status=status, **kwargs)
- self.save_payment_details(order)
- return order
-
- def add_payment_source(self, source):
- if self._payment_sources is None:
- self._payment_sources = []
- self._payment_sources.append(source)
-
- def add_payment_event(self, event_type_name, amount, reference=''):
- """
- Record a payment event for creation once the order is placed
- """
- event_type, __ = PaymentEventType.objects.get_or_create(
- name=event_type_name)
- # We keep a local cache of payment events
- if self._payment_events is None:
- self._payment_events = []
- event = PaymentEvent(
- event_type=event_type, amount=amount,
- reference=reference)
- self._payment_events.append(event)
-
- def handle_successful_order(self, order):
- """
- Handle the various steps required after an order has been successfully
- placed.
-
- Override this view if you want to perform custom actions when an
- order is submitted.
- """
- # Send confirmation message (normally an email)
- self.send_confirmation_message(order)
-
- # Flush all session data
- self.checkout_session.flush()
-
- # Save order id in session so thank-you page can load it
- self.request.session['checkout_order_id'] = order.id
-
- response = HttpResponseRedirect(self.get_success_url())
- self.send_signal(self.request, response, order)
- return response
-
- def send_signal(self, request, response, order):
- self.view_signal.send(
- sender=self, order=order, user=request.user,
- request=request, response=response)
-
- def get_success_url(self):
- return reverse('checkout:thank-you')
-
- def create_shipping_address(self, user, shipping_address):
- """
- Create and return the shipping address for the current order.
-
- Compared to self.get_shipping_address(), ShippingAddress is saved and
- makes sure that appropriate UserAddress exists.
- """
- shipping_address.save()
- if user.is_authenticated():
- self.update_address_book(user, shipping_address)
- return shipping_address
-
- def update_address_book(self, user, shipping_addr):
- """
- Update the user's address book based on the new shipping address
- """
- try:
- user_addr = user.addresses.get(
- hash=shipping_addr.generate_hash())
- except ObjectDoesNotExist:
- # Create a new user address
- user_addr = UserAddress(user=user)
- shipping_addr.populate_alternative_model(user_addr)
- user_addr.num_orders += 1
- user_addr.save()
-
- @deprecated
- def create_shipping_address_from_form_fields(self, addr_data):
- """Creates a shipping address model from the saved form fields"""
- shipping_addr = ShippingAddress(**addr_data)
- shipping_addr.save()
- return shipping_addr
-
- @deprecated
- def create_user_address(self, session_addr_data):
- """
- For signed-in users, we create a user address model which will go
- into their address book.
- """
- if self.request.user.is_authenticated():
- addr_data = session_addr_data.copy()
- addr_data['user_id'] = self.request.user.id
- user_addr = UserAddress(**addr_data)
- # Check that this address isn't already in the db as we don't want
- # to fill up the customer address book with duplicate addresses
- try:
- UserAddress._default_manager.get(
- hash=user_addr.generate_hash())
- except ObjectDoesNotExist:
- user_addr.save()
-
- @deprecated
- def create_shipping_address_from_user_address(self, addr_id):
- """Creates a shipping address from a user address"""
- address = UserAddress._default_manager.get(pk=addr_id)
- # Increment the number of orders to help determine popularity of orders
- address.num_orders += 1
- address.save()
-
- shipping_addr = ShippingAddress()
- address.populate_alternative_model(shipping_addr)
- shipping_addr.save()
- return shipping_addr
-
- def create_billing_address(self, billing_address=None, shipping_address=None, **kwargs):
- """
- Saves any relevant billing data (eg a billing address).
- """
- return None
-
- def save_payment_details(self, order):
- """
- Saves all payment-related details. This could include a billing
- address, payment sources and any order payment events.
- """
- self.save_payment_events(order)
- self.save_payment_sources(order)
-
- def save_payment_events(self, order):
- """
- Saves any relevant payment events for this order
- """
- if not self._payment_events:
- return
- for event in self._payment_events:
- event.order = order
- event.save()
- # We assume all lines are involved in the initial payment event
- for line in order.lines.all():
- PaymentEventQuantity.objects.create(
- event=event, line=line, quantity=line.quantity)
-
- def save_payment_sources(self, order):
- """
- Saves any payment sources used in this order.
-
- When the payment sources are created, the order model does not exist
- and so they need to have it set before saving.
- """
- if not self._payment_sources:
- return
- for source in self._payment_sources:
- source.order = order
- source.save()
-
- def get_initial_order_status(self, basket):
- return None
-
- def get_submitted_basket(self):
- basket_id = self.checkout_session.get_submitted_basket_id()
- return Basket._default_manager.get(pk=basket_id)
-
- def restore_frozen_basket(self):
- """
- Restores a frozen basket as the sole OPEN basket. Note that this also
- merges in any new products that have been added to a basket that has
- been created while payment.
- """
- try:
- fzn_basket = self.get_submitted_basket()
- except Basket.DoesNotExist:
- # Strange place. The previous basket stored in the session does
- # not exist.
- pass
- else:
- fzn_basket.thaw()
- if self.request.basket.id != fzn_basket.id:
- fzn_basket.merge(self.request.basket)
- self.request.basket = fzn_basket
-
- def send_confirmation_message(self, order, **kwargs):
- code = self.communication_type_code
- ctx = {'user': self.request.user,
- 'order': order,
- 'site': get_current_site(self.request),
- 'lines': order.lines.all()}
-
- if not self.request.user.is_authenticated():
- # Attempt to add the anon order status URL to the email template
- # ctx.
- try:
- path = reverse('customer:anon-order',
- kwargs={'order_number': order.number,
- 'hash': order.verification_hash()})
- except NoReverseMatch:
- # We don't care that much if we can't resolve the URL
- pass
- else:
- site = Site.objects.get_current()
- ctx['status_url'] = 'http://%s%s' % (site.domain, path)
-
- try:
- event_type = CommunicationEventType.objects.get(code=code)
- except CommunicationEventType.DoesNotExist:
- # No event-type in database, attempt to find templates for this
- # type and render them immediately to get the messages. Since we
- # have not CommunicationEventType to link to, we can't create a
- # CommunicationEvent instance.
- messages = CommunicationEventType.objects.get_and_render(code, ctx)
- event_type = None
- else:
- # Create CommunicationEvent
- CommunicationEvent._default_manager.create(
- order=order, event_type=event_type)
- messages = event_type.get_messages(ctx)
-
- if messages and messages['body']:
- logger.info("Order #%s - sending %s messages", order.number, code)
- dispatcher = Dispatcher(logger)
- dispatcher.dispatch_order_messages(order, messages,
- event_type, **kwargs)
- else:
- logger.warning("Order #%s - no %s communication event type",
- order.number, code)
|