Permalink
Browse files

Further tidy-up of refactored checkout classes

  • Loading branch information...
1 parent 37b2735 commit 34b4496537095bdc7dd3c865ff02c0e338c23456 @codeinthehole codeinthehole committed Jun 12, 2011
@@ -1,126 +0,0 @@
-from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseBadRequest
-from django.contrib import messages
-from django.core.urlresolvers import reverse
-
-from oscar.core.loading import import_module
-from oscar.apps.checkout.decorators import prev_steps_must_be_complete, basket_required
-import_module('checkout.calculators', ['OrderTotalCalculator'], locals())
-import_module('order.models', ['Order', 'ShippingAddress'], locals())
-import_module('checkout.utils', ['ProgressChecker', 'CheckoutSessionData'], locals())
-import_module('address.models', ['UserAddress'], locals())
-
-
-class CheckoutView(object):
- u"""
- Top-level superclass for all checkout view classes
- """
-
- def __init__(self, template_file=None):
- if template_file:
- self.template_file = template_file
-
- @basket_required
- @prev_steps_must_be_complete
- def __call__(self, request):
- u"""
- We forward request handling to the appropriate method
- based on the HTTP method.
- """
-
- # Set up the instance variables that are needed to place an order
- self.request = request
- self.co_data = CheckoutSessionData(request)
- self.basket = request.basket
-
- # Set up template context that is available to every view
- self.set_template_context(self.basket)
-
- # Forward method
- if request.method == 'POST':
- response = self.handle_POST()
- elif request.method == 'GET':
- response = self.handle_GET()
- else:
- response = HttpResponseBadRequest()
- return response
-
- def set_template_context(self, basket):
- method = self.co_data.shipping_method()
- if method:
- method.set_basket(basket)
- self.context = {'basket': basket,
- 'order_total': self.get_order_totals(basket)[0],
- 'shipping_addr': self.get_shipping_address()}
- self.set_shipping_context(method)
- self.set_payment_context()
-
- def set_shipping_context(self, method):
- if method:
- self.context['method'] = method
- self.context['shipping_total_excl_tax'] = method.basket_charge_excl_tax()
- self.context['shipping_total_incl_tax'] = method.basket_charge_incl_tax()
-
- def set_payment_context(self):
- method = self.co_data.payment_method()
- if method:
- self.context['payment_method'] = method
-
- def handle_GET(self):
- u"""
- Default behaviour is to set step as complete and redirect
- to the next step.
- """
- return self.get_success_response()
-
- def get_order_totals(self, basket):
- """
- Returns the total for the order with and without tax (as a tuple)
- """
- calc = OrderTotalCalculator(self.request)
- shipping_method = self.get_shipping_method(basket)
- total_incl_tax = calc.order_total_incl_tax(basket, shipping_method)
- total_excl_tax = calc.order_total_excl_tax(basket, shipping_method)
- return total_incl_tax, total_excl_tax
-
- def get_shipping_address(self):
- # Load address data into a blank address model
- addr_data = self.co_data.new_address_fields()
- if addr_data:
- return ShippingAddress(**addr_data)
- addr_id = self.co_data.user_address_id()
- if addr_id:
- try:
- return UserAddress._default_manager.get(pk=addr_id)
- except UserAddress.DoesNotExist:
- # This can happen if you reset all your tables and you still have
- # session data that refers to addresses that no longer exist
- pass
- return None
-
- def get_shipping_method(self, basket):
- u"""Returns the shipping method object"""
- method = self.co_data.shipping_method()
- if method:
- method.set_basket(basket)
- return method
-
- def get_success_response(self):
- u"""
- Returns the appropriate redirect response if a checkout
- step has been successfully passed.
- """
- self.mark_step_as_complete(self.request)
- return HttpResponseRedirect(reverse(self.get_next_step(self.request)))
-
- def mark_step_as_complete(self, request):
- """
- Convenience function for marking a checkout page
- as complete.
- """
- ProgressChecker().step_complete(request)
-
- def get_next_step(self, request):
- return ProgressChecker().get_next_step(request)
-
- def handle_POST(self):
- pass
@@ -5,70 +5,6 @@
import_module('shipping.repository', ['Repository'], locals())
-class ProgressChecker(object):
- u"""
- Class for testing whether the appropriate steps of the checkout
- have been completed.
- """
-
- # List of URL names that have to be completed (in this order)
- urls_for_steps = ['oscar-checkout-shipping-address',
- 'oscar-checkout-shipping-method',
- 'oscar-checkout-payment-method',
- 'oscar-checkout-preview',
- 'oscar-checkout-payment-details']
-
- def are_previous_steps_complete(self, request):
- u"""
- Checks whether the previous checkout steps have been completed.
-
- This uses the URL-name and the class-level list of required
- steps.
- """
- # Extract the URL name from the path
- complete_steps = self._get_completed_steps(request)
- try:
- url_name = self._get_url_name(request)
- current_step_index = self.urls_for_steps.index(url_name)
- last_completed_step_index = len(complete_steps) - 1
- return current_step_index <= last_completed_step_index + 1
- except ValueError:
- # Can't find current step index - must be manipulation
- return False
- except IndexError:
- # No complete steps - only allowed to be on first page
- return current_step_index == 0
-
- def step_complete(self, request):
- u"""Record a checkout step as complete."""
- url_name = self._get_url_name(request)
- complete_steps = self._get_completed_steps(request)
- if not url_name in complete_steps:
- # Only add name if this is the first time the step
- # has been completed.
- complete_steps.append(url_name)
- request.session['checkout_complete_steps'] = complete_steps
-
- def get_next_step(self, request):
- u"""Returns the next incomplete step of the checkout."""
- url_name = self._get_url_name(request)
- current_step_index = self.urls_for_steps.index(url_name)
- return self.urls_for_steps[current_step_index+1]
-
- def all_steps_complete(self, request):
- u"""
- Order has been submitted - clear the completed steps from
- the session.
- """
- request.session['checkout_complete_steps'] = []
-
- def _get_url_name(self, request):
- return resolve(request.path).url_name
-
- def _get_completed_steps(self, request):
- return request.session.get('checkout_complete_steps', [])
-
-
class CheckoutSessionData(object):
u"""Class responsible for marshalling all the checkout session data."""
SESSION_KEY = 'checkout_data'
@@ -18,16 +18,14 @@
from oscar.core.loading import import_module
import_module('checkout.forms', ['ShippingAddressForm'], locals())
import_module('checkout.calculators', ['OrderTotalCalculator'], locals())
-import_module('checkout.utils', ['ProgressChecker', 'CheckoutSessionData'], locals())
+import_module('checkout.utils', ['CheckoutSessionData'], locals())
import_module('checkout.signals', ['pre_payment', 'post_payment'], locals())
-import_module('checkout.core_views', ['CheckoutView'], locals())
import_module('order.models', ['Order', 'ShippingAddress', 'CommunicationEventType', 'CommunicationEvent'], locals())
import_module('order.utils', ['OrderNumberGenerator', 'OrderCreator'], locals())
import_module('address.models', ['UserAddress'], locals())
import_module('address.forms', ['UserAddressForm'], locals())
import_module('shipping.repository', ['Repository'], locals())
import_module('customer.models', ['Email'], locals())
-from oscar.apps.checkout.utils import CheckoutSessionData
import_module('payment.exceptions', ['RedirectRequiredException', 'UnableToTakePaymentException',
'PaymentException'], locals())
import_module('basket.models', ['Basket'], locals())
@@ -178,6 +176,10 @@ def get_success_url(self):
class CheckoutSessionMixin(object):
+ def dispatch(self, request, *args, **kwargs):
+ self.checkout_session = CheckoutSessionData(request)
+ return super(CheckoutSessionMixin, self).dispatch(request, *args, **kwargs)
+
def get_shipping_address(self):
"""
Return the current shipping address for this checkout session.
@@ -186,12 +188,11 @@ def get_shipping_address(self):
pre-populated (not saved), or a UserAddress model which will
need converting into a ShippingAddress model at submission
"""
- co_data = CheckoutSessionData(self.request)
- addr_data = co_data.new_address_fields()
+ addr_data = self.checkout_session.new_address_fields()
if addr_data:
# Load address data into a blank address model
return ShippingAddress(**addr_data)
- addr_id = co_data.user_address_id()
+ addr_id = self.checkout_session.user_address_id()
if addr_id:
try:
return UserAddress._default_manager.get(pk=addr_id)
@@ -202,12 +203,10 @@ def get_shipping_address(self):
return None
def use_shipping_method(self, method_code):
- co_data = CheckoutSessionData(self.request)
- co_data.use_shipping_method(method_code)
+ self.checkout_session.use_shipping_method(method_code)
def get_shipping_method(self, basket=None):
- co_data = CheckoutSessionData(self.request)
- method = co_data.shipping_method()
+ method = self.checkout_session.shipping_method()
if method:
if not basket:
basket = self.request.basket
@@ -227,12 +226,25 @@ def get_order_totals(self, basket=None, shipping_method=None):
total_excl_tax = calc.order_total_excl_tax(basket, shipping_method)
return total_incl_tax, total_excl_tax
- def flush_checkout_session(self):
- co_data = CheckoutSessionData(self.request)
- co_data.flush()
+ def get_context_data(self, **kwargs):
+ """
+ Assign common template variables to the context.
+ """
+ ctx = super(CheckoutSessionMixin, self).get_context_data(**kwargs)
+ ctx['shipping_address'] = self.get_shipping_address()
+ method = self.get_shipping_method()
+ if method:
+ ctx['shipping_method'] = method
+ ctx['shipping_total_excl_tax'] = method.basket_charge_excl_tax()
+ ctx['shipping_total_incl_tax'] = method.basket_charge_incl_tax()
+
+ ctx['order_total_incl_tax'], ctx['order_total_excl_tax'] = self.get_order_totals()
+
+ return ctx
-class ShippingMethodView(TemplateView, CheckoutSessionMixin):
+
+class ShippingMethodView(CheckoutSessionMixin, TemplateView):
"""
Shipping methods are domain-specific and so need implementing in a
subclass of this class.
@@ -250,7 +262,7 @@ def get(self, request, *args, **kwargs):
return super(ShippingMethodView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
- kwargs = super(ShippingMethodView, self).get_context_data(self, **kwargs)
+ kwargs = super(ShippingMethodView, self).get_context_data(**kwargs)
kwargs['methods'] = self._methods
return kwargs
@@ -272,7 +284,7 @@ def get_success_response(self):
return HttpResponseRedirect(reverse('oscar-checkout-payment-method'))
-class PaymentMethodView(TemplateView, CheckoutSessionMixin):
+class PaymentMethodView(CheckoutSessionMixin, TemplateView):
"""
View for a user to choose which payment method(s) they want to use.
@@ -287,34 +299,21 @@ def get_success_response(self):
return HttpResponseRedirect(reverse('oscar-checkout-preview'))
-class OrderPreviewView(TemplateView, CheckoutSessionMixin):
+class OrderPreviewView(CheckoutSessionMixin, TemplateView):
"""
View a preview of the order before submitting.
"""
template_name = 'oscar/checkout/preview.html'
-
- def get_context_data(self, **kwargs):
- ctx = super(OrderPreviewView, self).get_context_data(**kwargs)
- ctx['shipping_address'] = self.get_shipping_address()
-
- method = self.get_shipping_method()
- ctx['shipping_method'] = method
- ctx['shipping_total_excl_tax'] = method.basket_charge_excl_tax()
- ctx['shipping_total_incl_tax'] = method.basket_charge_incl_tax()
- ctx['order_total_incl_tax'], ctx['order_total_excl_tax'] = self.get_order_totals()
-
- return ctx
-class PaymentDetailsView(TemplateView, CheckoutSessionMixin):
+class PaymentDetailsView(CheckoutSessionMixin, TemplateView):
"""
For taking the details of payment and creating the order
The class is deliberately split into fine-grained method, responsible for only one
thing. This is to make it easier to subclass and override just one component of
functionality.
"""
- template_name = 'oscar/checkout/payment-details.html'
# Any payment sources should be added to this list as part of the
# _handle_payment method. If the order is placed successfully, then
@@ -448,9 +447,8 @@ def save_payment_sources(self, order):
def reset_checkout(self):
"""Reset any checkout session state"""
- self.flush_checkout_session()
+ self.checkout_session.flush()
self.request.session['checkout_basket_id'] = None
- ProgressChecker().all_steps_complete(self.request)
def create_order_models(self, basket, order_number, total_incl_tax, total_excl_tax):
"""Writes the order out to the DB"""
@@ -472,10 +470,19 @@ def get_initial_order_status(self, basket):
return None
def create_shipping_address(self):
- """Returns the shipping address"""
- co_data = CheckoutSessionData(self.request)
- addr_data = co_data.new_address_fields()
- addr_id = co_data.user_address_id()
+ """
+ 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.
+ """
+ addr_data = self.checkout_session.new_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)
@@ -28,7 +28,7 @@ class AbstractOrderAndItemLevelChargeMethod(models.Model, ShippingMethod):
price_per_item = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00'))
# If basket value is above this threshold, then shipping is free
- free_shipping_threshold = models.DecimalField(decimal_places=2, max_digits=12, null=True)
+ free_shipping_threshold = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
_basket = None
@@ -53,7 +53,7 @@
{% endif %}
<tr>
<td colspan="6">Order total</td>
- <td>{{ order_total|currency }}</td>
+ <td>{{ order_total_incl_tax|currency }}</td>
</tr>
</table>
<a href="{% url oscar-basket %}">Edit order contents</a>

0 comments on commit 34b4496

Please sign in to comment.