| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- import logging
-
- from django.http import HttpResponseRedirect
- from django.core.urlresolvers import reverse
- from django.contrib.sites.models import Site
- from django.core.exceptions import ObjectDoesNotExist
- from django.db.models import get_model
-
- from oscar.core.loading import get_class
- 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')
-
- # 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'
-
- def handle_order_placement(self, order_number, basket, total_incl_tax,
- total_excl_tax, user=None, **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, basket, total_incl_tax,
- total_excl_tax, user, **kwargs)
- basket.set_as_submitted()
- return self.handle_successful_order(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):
- event_type, __ = PaymentEventType.objects.get_or_create(
- name=event_type_name)
- if self._payment_events is None:
- self._payment_events = []
- event = PaymentEvent(event_type=event_type, amount=amount)
- 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
-
- return HttpResponseRedirect(self.get_success_url())
-
- def get_success_url(self):
- return reverse('checkout:thank-you')
-
- def place_order(self, order_number, basket, total_incl_tax,
- total_excl_tax, user=None, **kwargs):
- """
- Writes the order out to the DB including the payment models
- """
- shipping_address = self.create_shipping_address(basket)
- shipping_method = self.get_shipping_method(basket)
- billing_address = self.create_billing_address(shipping_address)
-
- if 'status' not in kwargs:
- status = self.get_initial_order_status(basket)
- else:
- status = kwargs.pop('status')
-
- # We allow a user to be passed in to handle cases where the order is
- # being placed on behalf of someone else.
- if user is None:
- user = self.request.user
-
- # Set guest email address for anon checkout. Some libraries (eg
- # PayPal) will pass this explicitly so we take care not to clobber.
- if (not self.request.user.is_authenticated() and 'guest_email'
- not in kwargs):
- kwargs['guest_email'] = self.checkout_session.get_guest_email()
-
- order = OrderCreator().place_order(basket=basket,
- total_incl_tax=total_incl_tax,
- total_excl_tax=total_excl_tax,
- user=user,
- shipping_method=shipping_method,
- shipping_address=shipping_address,
- billing_address=billing_address,
- order_number=order_number,
- status=status,
- **kwargs)
- self.save_payment_details(order)
- return order
-
- def create_shipping_address(self, basket=None):
- """
- Create and returns the shipping address for the current order.
-
- If the shipping address was entered manually, then we simply
- write out a ShippingAddress model with the appropriate form data. If
- the user is authenticated, then we create a UserAddress from this data
- too so it can be re-used in the future.
-
- If the shipping address was selected from the user's address book,
- then we convert the UserAddress to a ShippingAddress.
- """
- if not basket:
- basket = self.request.basket
- if not basket.is_shipping_required():
- return None
-
- addr_data = self.checkout_session.new_shipping_address_fields()
- addr_id = self.checkout_session.user_address_id()
- if addr_data:
- addr = self.create_shipping_address_from_form_fields(addr_data)
- self.create_user_address(addr_data)
- elif addr_id:
- addr = self.create_shipping_address_from_user_address(addr_id)
- else:
- raise AttributeError("No shipping address data found")
- return addr
-
- 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
-
- def create_user_address(self, 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['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()
-
- 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, shipping_address=None):
- """
- 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 = {'order': order,
- 'lines': order.lines.all()}
-
- if not self.request.user.is_authenticated():
- path = reverse('customer:anon-order',
- kwargs={'order_number': order.number,
- 'hash': order.verification_hash()})
- 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
- messages = CommunicationEventType.objects.get_and_render(code, ctx)
- event_type = None
- else:
- # Create order event
- 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)
|