Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: mirumee/satchless
base: e105124ac3
...
head fork: mirumee/satchless
compare: b562bbe4fc
  • 15 commits
  • 41 files changed
  • 0 commit comments
  • 2 contributors
Showing with 296 additions and 329 deletions.
  1. +3 −1 examples/demo/categories/app.py
  2. +1 −2  examples/demo/checkouts/app.py
  3. +6 −4 examples/demo/products/models.py
  4. +1 −1  examples/demo/settings.py
  5. +6 −1 examples/demo/static/css/style.css
  6. +7 −0 examples/demo/templates/satchless/cart/snippets/row.html
  7. +7 −6 examples/demo/templates/satchless/cart/view.html
  8. +2 −2 examples/demo/templates/satchless/cart/wishlist/view.html
  9. +2 −2 examples/demo/templates/satchless/category/view.html
  10. +2 −2 examples/demo/templates/satchless/product/view.html
  11. +12 −3 satchless/cart/app.py
  12. +1 −16 satchless/cart/models.py
  13. +6 −5 satchless/cart/templates/satchless/cart/view.html
  14. +3 −3 satchless/cart/templates/satchless/cart/view_ajax.html
  15. +32 −8 satchless/cart/templatetags/cart_prices.py
  16. +2 −16 satchless/cart/tests/app.py
  17. +11 −4 satchless/cart/tests/magic_app.py
  18. +0 −45 satchless/cart/tests/models.py
  19. +2 −2 satchless/cart/tests/templates/satchless/cart/view.html
  20. +47 −49 satchless/checkout/app.py
  21. +25 −24 satchless/checkout/tests/__init__.py
  22. +2 −1  satchless/contrib/checkout/multistep/app.py
  23. +16 −35 satchless/contrib/checkout/multistep/tests/__init__.py
  24. +2 −2 satchless/contrib/checkout/multistep/tests/templates/satchless/cart/view.html
  25. +3 −5 satchless/contrib/checkout/singlestep/tests/__init__.py
  26. +1 −2  satchless/contrib/order/partitioner/simple/__init__.py
  27. +9 −9 satchless/contrib/payment/authorizenet_provider/__init__.py
  28. +1 −3 satchless/contrib/payment/authorizenet_provider/forms.py
  29. +4 −2 satchless/contrib/payment/authorizenet_provider/models.py
  30. +8 −0 satchless/delivery/__init__.py
  31. +3 −2 satchless/delivery/tests.py
  32. +3 −22 satchless/order/handler.py
  33. +4 −11 satchless/order/models.py
  34. +3 −3 satchless/order/tests.py
  35. +15 −0 satchless/pricing/app.py
  36. +0 −6 satchless/pricing/handler.py
  37. +2 −0  satchless/pricing/tests.py
  38. +1 −1  satchless/product/templates/satchless/product/view.html
  39. +13 −13 satchless/product/templatetags/product_prices.py
  40. +23 −16 satchless/product/tests/pricing.py
  41. +5 −0 satchless/util/models.py
View
4 examples/demo/categories/app.py
@@ -1,9 +1,11 @@
import satchless.category.app
+from satchless.pricing.app import ProductAppPricingMixin
from . import models
import products.models
-class CategorizedProductApp(satchless.category.app.CategorizedProductApp):
+class CategorizedProductApp(ProductAppPricingMixin,
+ satchless.category.app.CategorizedProductApp):
Category = models.Category
Product = products.models.Product
Variant = products.models.Variant
View
3  examples/demo/checkouts/app.py
@@ -8,7 +8,6 @@
class CheckoutApp(app.MultiStepCheckoutApp):
- Cart = cart_app.Cart
Order = order_app.Order
BillingForm = modelform_factory(order_app.Order,
@@ -20,4 +19,4 @@ class CheckoutApp(app.MultiStepCheckoutApp):
form=forms.DeliveryMethodForm,
fields=forms.DeliveryMethodForm._meta.fields)
-checkout_app = CheckoutApp()
+checkout_app = CheckoutApp(cart_app)
View
10 examples/demo/products/models.py
@@ -12,19 +12,21 @@
from satchless.contrib.tax.flatgroups.models import TaxedProductMixin
from satchless.contrib.stock.singlestore.models import VariantStockLevelMixin
import satchless.product.models
+from satchless.utils.models import construct
from categories.models import Category
from sale.models import DiscountedProduct
-class Product(ProductPriceMixin, TaxedProductMixin, CategorizedProductMixin,
+class Product(ProductPriceMixin, TaxedProductMixin,
+ construct(CategorizedProductMixin, category=Category),
DiscountedProduct, satchless.product.models.Product):
- categories = models.ManyToManyField(Category,
- related_name='products',
- blank=True)
+
+ pass
class Variant(VariantPriceOffsetMixin, VariantStockLevelMixin, satchless.product.models.Variant):
+
pass
View
2  examples/demo/settings.py
@@ -226,7 +226,7 @@ def get_cache_key(self, *args, **kwargs):
'carts.handler.carts_handler',
]
SATCHLESS_ORDER_PARTITIONERS = [
- 'satchless.contrib.order.partitioner.simple.SimplePartitioner',
+ 'satchless.contrib.order.partitioner.simple.SimplePhysicalPartitioner',
]
SATCHLESS_DELIVERY_PROVIDERS = [
'satchless.contrib.delivery.simplepost.PostDeliveryProvider',
View
7 examples/demo/static/css/style.css
@@ -505,8 +505,13 @@ body.store.checkout {
margin: 0;
}
body.store.checkout #content table.cart-contents td.qty {
- padding-top: 20px
+ padding-top: 20px;
}
+ body.store.checkout #content table.cart-contents td.qty .errorlist {
+ margin: 0;
+ padding: 0;
+ font-size: 12px;
+ }
body.store.checkout #content table.cart-contents td.qty input {
text-align: right;
width: 3em;
View
7 examples/demo/templates/satchless/cart/snippets/row.html
@@ -42,6 +42,13 @@
<input type="submit" value="{% trans "update" %}" name="update" />
{% csrf_token %}
</form>
+ {% if form.errors %}
+ <ul class="errorlist">
+ {% for error in form.errors.values %}
+ <li>{{ error }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
{% else %}
&times; {{ item.quantity }}
{% endif %}
View
13 examples/demo/templates/satchless/cart/view.html
@@ -42,13 +42,14 @@
</tr>
</thead>
<tbody>
+ {% cart_total_price cart pricing_handler as cart_total %}
{% for form in cart_item_forms %}
{% with form.instance as cart_item %}
+ {% cartitem_unit_price cart_item pricing_handler as unit_price %}
+ {% cartitem_unit_price cart_item pricing_handler discount=0 as undiscounted_unit_price %}
+ {% cartitem_price cart_item pricing_handler as item_price %}
+ {% cartitem_price cart_item pricing_handler discount=0 as undiscounted_item_price %}
{% promote cart_item.variant as variant %}
- {% cartitem_unit_price cart_item as unit_price %}
- {% cartitem_unit_price cart_item discount=0 as undiscounted_unit_price %}
- {% cartitem_price cart_item as item_price %}
- {% cartitem_price cart_item discount=0 as undiscounted_item_price %}
{% with cart.currency as currency %}
{% include "satchless/cart/snippets/row.html" %}
{% endwith %}
@@ -58,13 +59,13 @@
<tfoot>
<tr class="checkout">
<td class="product-price" colspan="5">
- <span class="fullprice">{{ cart.get_total|gross|floatformat:2 }}</span>
+ <span class="fullprice">{{ cart_total|gross|floatformat:2 }}</span>
<span class="currency">{{ cart.currency }}</span><br />
</td>
</tr>
<tr class="checkout">
<td colspan="5">
- <form action="{% url checkout:prepare-order cart.token %}" method="post">
+ <form action="{% url checkout:prepare-order %}" method="post">
{% csrf_token %}
<button class="button" type="submit"><span>{% trans "Go to checkout" %}</span></button>
</form>
View
4 examples/demo/templates/satchless/cart/wishlist/view.html
@@ -47,8 +47,8 @@
<div class="grid_5 omega">
<div class="sidebar">
<div class="product-price">
- {% product_price_range product as price_range %}
- {% product_price_range product discount=0 as undiscounted_price_range %}
+ {% product_price_range product pricing_handler as price_range %}
+ {% product_price_range product pricing_handler discount=0 as undiscounted_price_range %}
{% if price_range.min != price_range.max %}
{% if price_range.min != undiscounted_price_range.min or price_range.max != undiscounted_price_range.max %}
<span class="strike">{{ undiscounted_price_range.min_price|gross|floatformat:2 }} - {{ undiscounted_price_range.max_price|gross|floatformat:2 }}</span>
View
4 examples/demo/templates/satchless/category/view.html
@@ -43,8 +43,8 @@
</span>
<h3>{{ product.name }}</h3>
<div class="price">
- {% product_price_range product as price_range %}
- {% product_price_range product discount=0 as undiscounted_price_range %}
+ {% product_price_range product pricing_handler as price_range %}
+ {% product_price_range product pricing_handler discount=0 as undiscounted_price_range %}
{% if price_range.min != undiscounted_price_range.min %}
<span class="strike">{{ undiscounted_price_range.min|gross|floatformat:2 }}</span>
{% endif %}
View
4 examples/demo/templates/satchless/product/view.html
@@ -39,7 +39,7 @@
</div>
</div>
-{% product_price_range product as price_range %}
+{% product_price_range product pricing_handler as price_range %}
<form action="" method="post" class="grid_16 omega">
<div class="product-details">
<div class="product-info">
@@ -63,7 +63,7 @@
<div class="add-to-cart">
{% if price_range %}
<div class="product-price">
- {% product_price_range product discount=0 as undiscounted_price_range %}
+ {% product_price_range product pricing_handler discount=0 as undiscounted_price_range %}
{% if price_range.min_price != price_range.max_price %}
{% if price_range.min_price != undiscounted_price_range.min_price or price_range.max_price != undiscounted_price_range.max_price %}
<span class="strike">{{ undiscounted_price_range.min_price|gross|floatformat:2 }} – {{ undiscounted_price_range.max_price|gross|floatformat:2 }}</span>
View
15 satchless/cart/app.py
@@ -27,7 +27,8 @@ class CartApp(SatchlessApp):
'satchless/cart/view.html'
]
- def __init__(self, *args, **kwargs):
+ def __init__(self, pricing_handler, *args, **kwargs):
+ self.pricing_handler = pricing_handler
super(CartApp, self).__init__(*args, **kwargs)
assert self.Cart, ('You need to subclass CartApp and provide Cart')
assert self.CartItemForm, ('You need to subclass CartApp and'
@@ -65,6 +66,8 @@ def cart(self, request):
context = self._handle_cart(cart, request)
if isinstance(context, HttpResponse):
return context
+ context = self.get_context_data(
+ request, pricing_handler=self.pricing_handler, **context)
format_data = {
'cart_type': self.cart_type,
}
@@ -92,8 +95,14 @@ class MagicCartApp(CartApp):
CartItem = None
AddToCartHandler = handler.AddToCartHandler
- def __init__(self, product_app, **kwargs):
+ def __init__(self, product_app, pricing_handler=None, **kwargs):
self.product_app = product_app
+ pricing_handler = pricing_handler or getattr(product_app,
+ 'pricing_handler', None)
+ if not pricing_handler:
+ raise ValueError('Requires either a pricing handler '
+ 'or product app with pricing handler')
+
self.Cart = self.Cart or self.construct_cart_class()
self.CartItem = (self.CartItem or
self.construct_cart_item_class(self.Cart,
@@ -104,7 +113,7 @@ def __init__(self, product_app, **kwargs):
if self.AddToCartHandler:
add_to_cart_handler = self.AddToCartHandler(cart_app=self)
product_app.register_product_view_handler(add_to_cart_handler)
- super(MagicCartApp, self).__init__(**kwargs)
+ super(MagicCartApp, self).__init__(pricing_handler=pricing_handler, **kwargs)
def construct_cart_class(self):
class Cart(models.Cart):
View
17 satchless/cart/models.py
@@ -134,11 +134,6 @@ def get_quantity(self, variant, **kwargs):
def is_empty(self):
return not self.items.exists()
- def get_total(self, **kwargs):
- from ..pricing import Price
- return sum([i.get_price(currency=self.currency, **kwargs) for i in self.get_all_items()],
- Price(0, currency=self.currency))
-
class CartItem(models.Model):
@@ -156,14 +151,4 @@ def __unicode__(self):
def save(self, *args, **kwargs):
assert self.quantity > 0
- super(CartItem, self).save(*args, **kwargs)
-
- def get_unit_price(self, currency=None, **kwargs):
- from ..pricing.handler import pricing_queue
- variant = self.variant.get_subtype_instance()
- currency = currency or self.cart.currency
- return pricing_queue.get_variant_price(variant, currency,
- quantity=self.quantity, cart=self.cart, cartitem=self, **kwargs)
-
- def get_price(self, currency=None, **kwargs):
- return self.get_unit_price(currency=currency, **kwargs) * self.quantity
+ return super(CartItem, self).save(*args, **kwargs)
View
11 satchless/cart/templates/satchless/cart/view.html
@@ -31,8 +31,8 @@
<td></td>
</tr>
{% endif %}
- {% cartitem_unit_price form.instance as unit_price %}
- {% cartitem_price form.instance as item_price %}
+ {% cartitem_unit_price form.instance pricing_handler as unit_price %}
+ {% cartitem_price form.instance pricing_handler as item_price %}
<tr class="item">
<td rowspan="2">{{ forloop.counter }}</td>
<td rowspan="2">{% promote form.instance.variant as variant %}
@@ -75,20 +75,21 @@
</tr>
{% endfor %}
</tbody>
+ {% cart_total_price cart pricing_handler as cart_total %}
<tfoot>
<tr class="total">
<th colspan="4" rowspan="2">{% trans "Total" %}:</th>
<td class="numerical">
- {{ cart.get_total|gross|floatformat:2 }} <span class="currency">{{ cart.currency }}</span><br />
+ {{ cart_total|gross|floatformat:2 }} <span class="currency">{{ cart.currency }}</span><br />
</td>
<td colspan="2" rowspan="2"></td>
</tr>
<tr>
- <td class="numerical netPrice">{{ cart.get_total|net|floatformat:2 }} <span class="currency">{{ cart.currency }}</span></td>
+ <td class="numerical netPrice">{{ cart_total|net|floatformat:2 }} <span class="currency">{{ cart.currency }}</span></td>
</tr>
</tfoot>
</table>
- <form action="{% url checkout:prepare-order cart_token=cart.token %}" method="post">
+ <form action="{% url checkout:prepare-order %}" method="post">
{% csrf_token %}
<button type="submit">{% trans "Check out" %}</button>
</form>
View
6 satchless/cart/templates/satchless/cart/view_ajax.html
@@ -24,8 +24,8 @@
<td></td>
</tr>
{% endif %}
- {% cartitem_unit_price form.instance as unit_price %}
- {% cartitem_price form.instance as item_price %}
+ {% cartitem_unit_price form.instance pricing_handler as unit_price %}
+ {% cartitem_price form.instance pricing_handler as item_price %}
<tr class="item">
<td rowspan="2">{{ forloop.counter }}</td>
<td rowspan="2">{% promote form.instance.variant as variant %}
@@ -81,7 +81,7 @@
</tr>
</tfoot>
</table>
- <form action="{% url checkout:prepare-order cart_token=cart.token %}" method="post">
+ <form action="{% url checkout:prepare-order %}" method="post">
{% csrf_token %}
<button type="submit">{% trans "Check out" %}</button>
</form>
View
40 satchless/cart/templatetags/cart_prices.py
@@ -1,22 +1,38 @@
from django import template
+from ...pricing import Price
from ...product.templatetags.product_prices import BasePriceNode, parse_price_tag
register = template.Library()
-class CartItemPriceNode(BasePriceNode):
+class CartItemUnitPriceNode(BasePriceNode):
def get_currency_for_item(self, item):
return item.cart.currency
- def get_price(self, cartitem, currency, **kwargs):
- return cartitem.get_price(currency=currency, **kwargs)
+ def get_price(self, cartitem, pricing_handler, currency, **kwargs):
+ return pricing_handler.get_variant_price(cartitem.variant.get_subtype_instance(),
+ currency=currency,
+ quantity=cartitem.quantity,
+ cart=cartitem.cart,
+ cartitem=cartitem, **kwargs)
-class CartItemUnitPriceNode(BasePriceNode):
- def get_currency_for_item(self, item):
- return item.cart.currency
- def get_price(self, cartitem, currency, **kwargs):
- return cartitem.get_unit_price(currency=currency, **kwargs)
+class CartItemPriceNode(CartItemUnitPriceNode):
+ def get_price(self, cartitem, *args, **kwargs):
+ unit_price = super(CartItemPriceNode, self).get_price(cartitem, *args, **kwargs)
+ return unit_price*cartitem.quantity
+
+
+class CartTotalPriceNode(BasePriceNode):
+ def get_currency_for_item(self, cart):
+ return cart.currency
+
+ def get_price(self, cart, pricing_handler, currency, **kwargs):
+ get_variant_price = lambda cart_item: pricing_handler.get_variant_price(
+ quantity=cart_item.quantity, currency=currency,
+ variant=cart_item.variant.get_subtype_instance(), **kwargs)
+ return sum([get_variant_price(ci)*ci.quantity for ci in cart.get_all_items()],
+ Price(0, currency=currency))
@register.tag
def cartitem_price(parser, token):
@@ -33,3 +49,11 @@ def cartitem_unit_price(parser, token):
except (ImportError, NotImplementedError):
pass
return ''
+
+@register.tag
+def cart_total_price(parser, token):
+ try:
+ return CartTotalPriceNode(*parse_price_tag(parser, token))
+ except (ImportError, NotImplementedError):
+ pass
+ return ''
View
18 satchless/cart/tests/app.py
@@ -5,12 +5,10 @@
from django.conf.urls.defaults import patterns, include, url
from django.core.exceptions import ObjectDoesNotExist
import django.forms
-from django.http import HttpResponse
from .. import app
from .. import forms
-from ...checkout.app import CheckoutApp
-from ...pricing import handler as pricing_handler, Price
+from ...pricing import handler as Price
from ...product.app import ProductApp
from ...product.tests.pricing import FiveZlotyPriceHandler
from ...util.tests import ViewsTestCase
@@ -138,18 +136,8 @@ def get_cart_for_request(self, request):
return cart
-class TestCheckoutApp(CheckoutApp):
-
- Cart = MockCart
- Order = MockOrder
- def prepare_order(self, *args, **kwargs):
- return HttpResponse("OK")
-
-
-
product_app = TestProductApp()
-checkout_app = TestCheckoutApp()
-cart_app = TestCartApp()
+cart_app = TestCartApp(pricing_handler=FiveZlotyPriceHandler())
class AppTestCase(ViewsTestCase):
@@ -157,7 +145,6 @@ class urls:
urlpatterns = patterns('',
url(r'^cart/', include(cart_app.urls)),
url(r'^products/', include(product_app.urls)),
- url(r'^checkout/', include(checkout_app.urls))
)
def setUp(self):
@@ -170,7 +157,6 @@ def setUp(self):
os.path.join(test_dir, '..', 'templates')]
}
self.original_settings = self._setup_settings(self.custom_settings)
- pricing_handler.pricing_queue = pricing_handler.PricingQueue(FiveZlotyPriceHandler)
self.cart = cart_app.Cart()
self.variant_1 = MockProduct('macaw_blue')
View
15 satchless/cart/tests/magic_app.py
@@ -5,16 +5,24 @@
from django.test import Client
import os
-from ...pricing import handler as pricing_handler
+from ...pricing.app import ProductAppPricingMixin
+from ...product.app import MagicProductApp
from ...product.tests.pricing import FiveZlotyPriceHandler
-from ...product.tests import (DeadParrot, ZombieParrot, DeadParrotVariantForm,
- product_app)
+from ...product.tests import Parrot, ParrotVariant, DeadParrot, ZombieParrot, DeadParrotVariantForm
from ...util.tests import ViewsTestCase
from .. import app
from . import TestCart, TestCartItem
+class TestProductApp(ProductAppPricingMixin, MagicProductApp):
+
+ Product = Parrot
+ Variant = ParrotVariant
+
+product_app = TestProductApp(pricing_handler=FiveZlotyPriceHandler())
+
+
class TestCartApp(app.MagicCartApp):
app_name = 'test_cart_app'
cart_type = 'test_cart_app'
@@ -66,7 +74,6 @@ def setUp(self):
os.path.join(test_dir, 'templates')]
}
self.original_settings = self._setup_settings(self.custom_settings)
- pricing_handler.pricing_queue = pricing_handler.PricingQueue(FiveZlotyPriceHandler)
def tearDown(self):
self._teardown_settings(self.original_settings,
View
45 satchless/cart/tests/models.py
@@ -1,34 +1,11 @@
from decimal import Decimal
-from django.conf import settings
-from django.contrib.auth.models import User
from django.test import TestCase
-from satchless.pricing import Price, PriceRange, PricingHandler
-
from ...product.tests import DeadParrot
-from ...pricing import handler as pricing_handler
from .. import signals
from . import cart_app
-class SuperuserDiscountPriceHandler(PricingHandler):
- """Everything has $5 price, but if you are superuser you get everything for free"""
-
- def get_variant_price(self, *args, **kwargs):
- user = kwargs.get('user', None)
- if user and user.is_superuser:
- return Price(net=0, gross=0, currency=u'USD')
- return Price(net=5, gross=5, currency=u'USD')
-
- def get_product_price_range(self, *args, **kwargs):
- user = kwargs.get('user', None)
- if user and user.is_superuser:
- return PriceRange(min_price=Price(net=0, gross=0, currency=u'USD'),
- max_price=Price(net=0, gross=0, currency=u'USD'))
- return PriceRange(min_price=Price(net=5, gross=5, currency=u'USD'),
- max_price=Price(net=5, gross=5, currency=u'USD'))
-
-
class ModelsTestCase(TestCase):
def setUp(self):
@@ -42,13 +19,6 @@ def setUp(self):
self.cockatoo_a = self.cockatoo.variants.create(looks_alive=True)
self.cockatoo_d = self.cockatoo.variants.create(looks_alive=False)
- self.original_pricing_queue = pricing_handler.pricing_queue
- pricing_handler.pricing_queue = pricing_handler.PricingQueue(
- SuperuserDiscountPriceHandler)
-
- def tearDown(self):
- pricing_handler.pricing_queue = self.original_pricing_queue
-
def test_replace_quantity_for_non_existing_item(self):
cart = cart_app.Cart.objects.create(typ='satchless.test_cart')
@@ -173,18 +143,3 @@ def modify_qty(sender, instance=None, variant=None, old_quantity=None,
(1, 0, u"Parrots don't rest in groups"))
self.assertEqual(1, cart.get_quantity(self.cockatoo_d))
- def test_cart_uses_whole_context_when_calculates_total_price(self):
- cart = cart_app.Cart.objects.create(typ='satchless', currency='USD')
- cart.replace_item(self.macaw_a, 1)
-
- superuser = User.objects.create_superuser(
- username='superman', password='pass', email='superman@example.com')
- regular_user = User.objects.create_user(
- username='user', password='pass', email='user@example.com')
-
- self.assertEqual(cart.get_total(user=superuser),
- Price(net=0, gross=0, currency=u'USD'))
- self.assertEqual(cart.get_total(user=regular_user),
- Price(net=5, gross=5, currency=u'USD'))
- self.assertEqual(cart.get_total(),
- Price(net=5, gross=5, currency=u'USD'))
View
4 satchless/cart/tests/templates/satchless/cart/view.html
@@ -31,8 +31,8 @@
<td></td>
</tr>
{% endif %}
- {% cartitem_unit_price form.instance as unit_price %}
- {% cartitem_price form.instance as item_price %}
+ {% cartitem_unit_price form.instance pricing_handler as unit_price %}
+ {% cartitem_price form.instance pricing_handler as item_price %}
<tr class="item">
<td rowspan="2">{{ forloop.counter }}</td>
<td rowspan="2">{% promote form.instance.variant as variant %}
View
96 satchless/checkout/app.py
@@ -1,6 +1,7 @@
from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
-from django.shortcuts import get_object_or_404, redirect
+from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views.decorators.http import require_POST
@@ -9,45 +10,42 @@
from ..order import handler
from ..order.signals import order_pre_confirm
from ..payment import PaymentFailure, ConfirmationFormNeeded
+from ..contrib.order.partitioner.simple import SimplePartitioner
class CheckoutApp(SatchlessApp):
+
app_name = 'checkout'
namespace = 'checkout'
- Cart = None
- cart_type = 'cart'
+ order_session_key = 'checkout-order'
Order = None
confirmation_templates = [
'satchless/checkout/confirmation.html',
]
- def __init__(self, *args, **kwargs):
- delivery_providers = kwargs.pop('delivery_providers',
- getattr(settings, 'SATCHLESS_DELIVERY_PROVIDERS', []))
- self.delivery_queue = handler.DeliveryQueue(*delivery_providers)
+ def __init__(self, cart_app, *args, **kwargs):
+ self.cart_app = cart_app
+ self.delivery_queue = kwargs.pop('delivery_provider',
+ handler.DeliveryQueue(*getattr(settings, 'SATCHLESS_DELIVERY_PROVIDERS', [])))
- payment_providers = kwargs.pop('payment_providers',
- getattr(settings, 'SATCHLESS_PAYMENT_PROVIDERS', []))
- self.payment_queue = handler.PaymentQueue(*payment_providers)
+ self.payment_queue = kwargs.pop('payment_provider',
+ handler.PaymentQueue(*getattr(settings, 'SATCHLESS_PAYMENT_PROVIDERS', [])))
- partitioners = kwargs.pop(
- 'partitioners', getattr(settings, 'SATCHLESS_ORDER_PARTITIONERS',
- ['satchless.contrib.order.partitioner.simple.SimplePartitioner']))
- self.partitioner_queue = handler.PartitionerQueue(*partitioners)
+ self.delivery_partitioner = kwargs.pop('delivery_partitioner',
+ handler.PartitionerQueue(*getattr(settings, 'SATCHLESS_ORDER_PARTITIONERS',
+ [SimplePartitioner])))
super(CheckoutApp, self).__init__(*args, **kwargs)
assert self.Order, ('You need to subclass CheckoutApp and provide Order')
- assert self.Cart, ('You need to subclass CheckoutApp and provide Cart')
def get_order(self, request, order_token):
- user = request.user if request.user.is_authenticated() else None
try:
- return self.Order.objects.get(token=order_token, user=user)
+ return self.Order.objects.get(token=order_token)
except self.Order.DoesNotExist:
return
def redirect_order(self, order):
if not order or order.is_empty():
- return redirect('cart:details')
+ return self.cart_app.redirect('details')
elif order.status == 'checkout':
return self.redirect('checkout',
order_token=order.token)
@@ -57,13 +55,18 @@ def redirect_order(self, order):
return redirect('order:details', order_token=order.token)
def partition_cart(self, cart, order, **pricing_context):
- groups = filter(None, self.partitioner_queue.partition(cart))
- for group in groups:
- delivery_group = order.create_delivery_group(group)
- for item in group:
- delivery_group.add_item(item.variant, item.quantity,
- price=item.get_price(**pricing_context))
- return order
+ delivery_groups, remaining_items = self.delivery_partitioner.partition(
+ cart, cart.get_all_items())
+ if remaining_items:
+ raise ImproperlyConfigured('Unhandled items remaining in cart.')
+ for delivery_group in filter(None, delivery_groups):
+ order_delivery_group = order.create_delivery_group(delivery_group)
+ for cartitem in delivery_group:
+ price = self.cart_app.pricing_handler.get_variant_price(
+ cartitem.variant.get_subtype_instance(), currency=cart.currency,
+ quantity=cartitem.quantity, cart=cartitem.cart,
+ cartitem=cartitem, **pricing_context)
+ order_delivery_group.add_item(cartitem.variant, cartitem.quantity, price)
def get_order_from_cart(self, request, cart, order=None):
if not order:
@@ -77,32 +80,27 @@ def get_order_from_cart(self, request, cart, order=None):
previous_orders.delete()
return order
- @view(r'^prepare/(?P<cart_token>\w+)/$', name='prepare-order')
+ @view(r'^prepare-order/$', name='prepare-order')
@method_decorator(require_POST)
- def prepare_order(self, request, cart_token):
- cart = get_object_or_404(self.Cart, token=cart_token,
- typ=self.cart_type)
+ def prepare_order(self, request):
+ cart = self.cart_app.get_cart_for_request(request)
if cart.is_empty():
- return redirect('cart:details')
-
- order_pk = request.session.get('satchless_order')
- try:
- order = self.Order.objects.get(pk=order_pk, cart=cart,
- status='checkout')
- except self.Order.DoesNotExist:
+ return self.cart_app.redirect('details')
+
+ order_pk = request.session.get(self.order_session_key)
+ order = None
+ if order_pk:
+ try:
+ order = self.Order.objects.get(pk=order_pk, cart=cart,
+ status='checkout')
+ except self.Order.DoesNotExist:
+ pass
+ if not order or order.is_empty():
order = self.get_order_from_cart(request, cart)
- else:
- if order.is_empty():
- order = self.get_order_from_cart(request, cart, order)
- if request.user.is_authenticated():
- if cart.owner != request.user:
- cart.owner = request.user
- cart.save()
- if order.user != request.user:
- order.user = request.user
- order.save()
-
- request.session['satchless_order'] = order.pk
+ if request.user.is_authenticated() and order.user != request.user:
+ order.user = request.user
+ order.save()
+ request.session[self.order_session_key] = order.pk
return self.redirect('checkout', order_token=order.token)
@view(r'^(?P<order_token>\w+)/reactivate/$', name='reactivate-order')
@@ -138,4 +136,4 @@ def confirmation(self, request, order_token):
order.set_status('payment-failed')
else:
order.set_status('payment-complete')
- return redirect('order:details', order_token=order.token)
+ return redirect('order:details', order_token=order.token)
View
49 satchless/checkout/tests/__init__.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
+import os
+
from django.conf.urls.defaults import patterns, include, url
-from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.test import Client
@@ -9,9 +10,7 @@
from ...cart.tests import cart_app
from ...core.app import view
from ...order.tests import order_app
-from ...pricing import handler as pricing_handler
from ...product.tests import DeadParrot
-from ...product.tests.pricing import FiveZlotyPriceHandler
from ...util.tests import ViewsTestCase
@@ -24,6 +23,19 @@ def __init__(self, checkout_app):
url(r'^order/', include(order_app.urls)),
)
+ def setUp(self):
+ test_dir = os.path.dirname(__file__)
+ self.custom_settings = {
+ 'TEMPLATE_DIRS': [os.path.join(test_dir, 'templates'),
+ os.path.join(test_dir, '..', '..', 'cart', 'tests', 'templates')]
+ }
+ self.original_settings = self._setup_settings(self.custom_settings)
+
+ def tearDown(self):
+ self._teardown_settings(self.original_settings,
+ self.custom_settings)
+
+
def _create_cart(self, client):
cart = self._get_or_create_cart_for_client(client)
cart.replace_item(self.macaw_blue, 1)
@@ -33,27 +45,25 @@ def _get_or_create_cart_for_client(self, client):
self._test_status(cart_app.reverse('details'),
client_instance=client)
token = client.session[cart_app.cart_session_key]
- return self.checkout_app.Cart.objects.get(token=token, typ=cart_app.cart_type)
+ return self.checkout_app.cart_app.Cart.objects.get(token=token, typ=cart_app.cart_type)
def _get_or_create_order_for_client(self, client):
- cart = self._get_or_create_cart_for_client(client)
+ self._get_or_create_cart_for_client(client)
self._test_status(
- self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token': cart.token}),
+ self.checkout_app.reverse('prepare-order'),
method='post', client_instance=client, status_code=302)
- order_pk = client.session.get('satchless_order', None)
+ order_pk = client.session.get(self.checkout_app.order_session_key, None)
return self.checkout_app.Order.objects.get(pk=order_pk)
def _create_order(self, client):
- cart = self._create_cart(client)
+ self._create_cart(client)
self._test_status(
- self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token': cart.token}),
+ self.checkout_app.reverse('prepare-order'),
method='post', client_instance=client, status_code=302)
return self._get_order_from_session(client.session)
def _get_order_from_session(self, session):
- order_pk = session.get('satchless_order', None)
+ order_pk = session.get(self.checkout_app.order_session_key, None)
if order_pk:
return self.checkout_app.Order.objects.get(pk=order_pk)
return None
@@ -68,8 +78,6 @@ def _get_order_items(self, order):
class MockCheckoutApp(CheckoutApp):
- cart_type = cart_app.cart_type
- Cart = cart_app.Cart
Order = order_app.Order
@view(r'^(?P<order_token>\w+)/$', name='checkout')
@@ -77,24 +85,17 @@ def checkout(self, *args, **kwargs):
return HttpResponse()
-class App(BaseCheckoutAppTests):
- checkout_app = MockCheckoutApp()
+class AppTestCase(BaseCheckoutAppTests):
+ checkout_app = MockCheckoutApp(cart_app=cart_app)
urls = BaseCheckoutAppTests.MockUrls(checkout_app)
def setUp(self):
+ super(AppTestCase, self).setUp()
self.anon_client = Client()
self.macaw = DeadParrot.objects.create(slug='macaw',
species="Hyacinth Macaw")
self.macaw_blue = self.macaw.variants.create(color='blue',
looks_alive=False)
- self.original_handlers = settings.SATCHLESS_PRICING_HANDLERS
- pricing_handler.pricing_queue = pricing_handler.PricingQueue(
- FiveZlotyPriceHandler)
-
- def tearDown(self):
- #self._teardown_settings(self.original_settings, self.custom_settings)
- pricing_handler.pricing_queue = pricing_handler.PricingQueue(
- *self.original_handlers)
def test_reactive_order_view_redirects_to_checkout_for_correct_order(self):
order = self._create_order(self.anon_client)
View
3  satchless/contrib/checkout/multistep/app.py
@@ -7,6 +7,7 @@
from ....order import forms
class MultiStepCheckoutApp(app.CheckoutApp):
+
checkout_templates = [
'satchless/checkout/checkout.html'
]
@@ -30,7 +31,7 @@ class MultiStepCheckoutApp(app.CheckoutApp):
ShippingFormSet = None
def __init__(self, *args, **kwargs):
- super(MultiStepCheckoutApp, self).__init__(self, *args, **kwargs)
+ super(MultiStepCheckoutApp, self).__init__(*args, **kwargs)
assert ((self.ShippingForm or self.ShippingFormSet) and
(self.DeliveryMethodFormSet or self.DeliveryMethodForm) and
self.BillingForm), (
View
51 satchless/contrib/checkout/multistep/tests/__init__.py
@@ -2,7 +2,6 @@
import os
from decimal import Decimal
-from django.conf import settings
from django.core.urlresolvers import reverse
from django.forms.models import modelform_factory
from django import forms
@@ -15,10 +14,7 @@
from .....order import forms as order_forms
from .....payment import ConfirmationFormNeeded
from .....payment.tests import TestPaymentProvider
-from .....pricing import handler as pricing_handler
from .....product.tests import DeadParrot
-from .....product.tests.pricing import FiveZlotyPriceHandler
-
from .. import app
from .....cart.tests import cart_app
@@ -41,8 +37,6 @@ def get_configuration_form(self, order, data, typ=None):
class TestCheckoutApp(app.MultiStepCheckoutApp):
- Cart = cart_app.Cart
- cart_type = cart_app.cart_type
Order = order_app.Order
BillingForm = modelform_factory(order_app.Order,
@@ -56,11 +50,12 @@ class TestCheckoutApp(app.MultiStepCheckoutApp):
-class CheckoutTest(BaseCheckoutAppTests):
+class CheckoutTestCase(BaseCheckoutAppTests):
checkout_app = TestCheckoutApp(
- delivery_providers=[TestDeliveryProvider],
- payment_providers=[TestPaymentProviderWithConfirmation],
- partitioners=[SimplePhysicalPartitioner])
+ cart_app=cart_app,
+ delivery_provider=TestDeliveryProvider(),
+ payment_provider=TestPaymentProviderWithConfirmation(),
+ delivery_partitioner=SimplePhysicalPartitioner())
urls = BaseCheckoutAppTests.MockUrls(checkout_app=checkout_app)
def setUp(self):
@@ -87,7 +82,8 @@ def setUp(self):
'SATCHLESS_DJANGO_PAYMENT_TYPES': ['dummy'],
'PAYMENT_VARIANTS': {'dummy': ('payments.dummy.DummyProvider',
{'url': '/', })},
- 'TEMPLATE_DIRS': (os.path.join(satchless_dir, 'category', 'templates'),
+ 'TEMPLATE_DIRS': (os.path.join(satchless_dir, 'cart', 'templates'),
+ os.path.join(satchless_dir, 'category', 'templates'),
os.path.join(satchless_dir, 'order', 'templates'),
os.path.join(test_dir, '..', 'templates'),
os.path.join(test_dir, 'templates')),
@@ -107,14 +103,8 @@ def setUp(self):
PostShippingType.objects.create(price=20, typ='list',
name='List zwykly')
- self.original_handlers = settings.SATCHLESS_PRICING_HANDLERS
- pricing_handler.pricing_queue = pricing_handler.PricingQueue(
- FiveZlotyPriceHandler)
-
def tearDown(self):
self._teardown_settings(self.original_settings, self.custom_settings)
- pricing_handler.pricing_queue = pricing_handler.PricingQueue(
- *self.original_handlers)
def test_order_from_cart_view_creates_proper_order(self):
cart = self._get_or_create_cart_for_client(self.anon_client)
@@ -122,9 +112,7 @@ def test_order_from_cart_view_creates_proper_order(self):
cart.replace_item(self.macaw_blue_fake, Decimal('2.45'))
cart.replace_item(self.cockatoo_white_a, Decimal('2.45'))
- self._test_status(self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token':
- cart.token}),
+ self._test_status(self.checkout_app.reverse('prepare-order'),
method='post', client_instance=self.anon_client,
status_code=302)
@@ -141,9 +129,7 @@ def test_partitioned_correctly_after_cart_changes(self):
cart.replace_item(self.macaw_blue_fake, Decimal('2.45'))
cart.replace_item(self.cockatoo_white_a, Decimal('2.45'))
- self._test_status(self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token':
- cart.token}),
+ self._test_status(self.checkout_app.reverse('prepare-order'),
method='post', client_instance=self.anon_client,
status_code=302)
@@ -158,9 +144,7 @@ def test_partitioned_correctly_after_cart_changes(self):
cart.add_item(self.macaw_blue_fake, 100)
# repartition
- self._test_status(self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token':
- cart.token}),
+ self._test_status(self.checkout_app.reverse('prepare-order'),
method='post', client_instance=self.anon_client,
status_code=302)
order = self._get_order_from_session(self.anon_client.session)
@@ -173,10 +157,9 @@ def test_prepare_order_creates_order_and_redirects_to_checkout_when_cart_is_not_
cart = self._get_or_create_cart_for_client(self.anon_client)
cart.replace_item(self.macaw_blue, 1)
response = self._test_status(
- self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token': cart.token}),
+ self.checkout_app.reverse('prepare-order'),
method='post', client_instance=self.anon_client, status_code=302)
- order_pk = self.anon_client.session.get('satchless_order', None)
+ order_pk = self.anon_client.session.get(self.checkout_app.order_session_key, None)
order = self.checkout_app.Order.objects.get(pk=order_pk)
self.assertRedirects(response,
self.checkout_app.reverse('checkout',
@@ -184,19 +167,17 @@ def test_prepare_order_creates_order_and_redirects_to_checkout_when_cart_is_not_
order.token}))
def test_prepare_order_redirects_to_cart_when_cart_is_empty(self):
- cart = self._get_or_create_cart_for_client(self.anon_client)
+ self._get_or_create_cart_for_client(self.anon_client)
response = self._test_status(
- self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token': cart.token}),
+ self.checkout_app.reverse('prepare-order'),
method='post', client_instance=self.anon_client, status_code=302)
self.assertRedirects(response, reverse('cart:details'))
def test_prepare_order_redirects_to_checkout_when_order_exists_and_is_not_empty(self):
- cart = self._get_or_create_cart_for_client(self.anon_client)
+ self._get_or_create_cart_for_client(self.anon_client)
order = self._create_order(self.anon_client)
response = self._test_status(
- self.checkout_app.reverse('prepare-order',
- kwargs={'cart_token': cart.token}),
+ self.checkout_app.reverse('prepare-order'),
method='post', client_instance=self.anon_client, status_code=302)
self.assertRedirects(response,
self.checkout_app.reverse('checkout',
View
4 satchless/contrib/checkout/multistep/tests/templates/satchless/cart/view.html
@@ -23,8 +23,8 @@
<td></td>
</tr>
{% endif %}
- {% cartitem_unit_price form.instance as unit_price %}
- {% cartitem_price form.instance as item_price %}
+ {% cartitem_unit_price form.instance pricing_handler as unit_price %}
+ {% cartitem_price form.instance pricing_handler as item_price %}
<tr class="item">
<td rowspan="2">{{ forloop.counter }}</td>
<td rowspan="2">{% promote form.instance.variant as variant %}
View
8 satchless/contrib/checkout/singlestep/tests/__init__.py
@@ -26,13 +26,12 @@ def confirm(self, order, typ=None):
class TestSingleStepCheckoutApp(SingleStepCheckoutApp):
Order = order_app.Order
- Cart = cart_app.Cart
- cart_type = cart_app.cart_type
BillingForm = modelform_factory(order_app.Order,
BillingForm)
-checkout_app = TestSingleStepCheckoutApp(delivery_providers=[TestDeliveryProvider],
- payment_providers=[TestPaymentProviderWithConfirmation])
+checkout_app = TestSingleStepCheckoutApp(cart_app=cart_app,
+ delivery_provider=TestDeliveryProvider(),
+ payment_provider=TestPaymentProviderWithConfirmation())
class App(BaseCheckoutAppTests):
@@ -40,7 +39,6 @@ class App(BaseCheckoutAppTests):
urls = BaseCheckoutAppTests.MockUrls(checkout_app=checkout_app)
def setUp(self):
- checkout_app.cart_model = cart_app.Cart
checkout_app.order_model = order_app.Order
self.parrot = DeadParrot.objects.create(slug='parrot',
species='Hyacinth Macaw')
View
3  satchless/contrib/order/partitioner/simple/__init__.py
@@ -12,8 +12,7 @@ class SimplePartitioner(Partitioner):
def partition(self, cart, items):
handled_groups = [Partition(list(items), shipping=self.shipping)]
- remaining_items = ()
- return handled_groups, remaining_items
+ return handled_groups, ()
class SimplePhysicalPartitioner(SimplePartitioner):
View
18 satchless/contrib/payment/authorizenet_provider/__init__.py
@@ -23,7 +23,8 @@ def get_configuration_form(self, order, typ, data):
def save(self, order, typ, form):
order.payment_price = 0
- order.payment_type_name = typ.name
+ payment_type = next(t for t in self.enum_types() if t.typ == typ)
+ order.payment_type_name = payment_type.name
order.save()
form.save()
@@ -65,17 +66,16 @@ def get_billing_data(self, order):
return result
def confirm(self, order, typ=None):
- v = order.paymentvariant.get_subtype_instance()
trans_type = self.capture and 'AUTH_CAPTURE' or 'AUTH_ONLY'
data = {
- 'card_num': v.cc_number,
- 'exp_date': v.cc_expiration,
+ 'card_num': order.payment.cc_number,
+ 'exp_date': order.payment.cc_expiration,
'amount': order.get_total().gross,
'invoice_num': order.pk,
'type': trans_type,
}
- if v.cc_cvv2:
- data['card_code'] = v.cc_cvv2
+ if order.payment.cc_cvv2:
+ data['card_code'] = order.payment.cc_cvv2
data.update(self.get_billing_data(order))
data.update(self.get_shipping_data(order))
data = dict((k, unidecode(v) if isinstance(v, unicode) else v)
@@ -84,9 +84,9 @@ def confirm(self, order, typ=None):
response = process_payment(data, {})
except urllib2.URLError:
raise PaymentFailure(ugettext("Could not connect to the gateway."))
- v.cc_cvv2 = '' # Forget the CVV2 number immediately after the transaction
- v.response = response
- v.save()
+ order.payment.cc_cvv2 = '' # Forget the CVV2 number immediately after the transaction
+ order.payment.response = response
+ order.payment.save()
if not response.is_approved:
raise PaymentFailure(response.response_reason_text)
View
4 satchless/contrib/payment/authorizenet_provider/forms.py
@@ -9,7 +9,6 @@
_('Enter a valid security number.'))
class PaymentForm(forms.ModelForm):
- billing_email = forms.EmailField(label=_('Email Address'))
cc_name = forms.CharField(label=_('Name on Credit Card'), max_length=128)
cc_number = CreditCardNumberField(label=_('Card Number'), max_length=32,
required=True)
@@ -18,5 +17,4 @@ class PaymentForm(forms.ModelForm):
label=_('CVV2 Security Number'), max_length=4)
class Meta:
- fields = ('cc_name', 'cc_number', 'cc_expiration', 'cc_cvv2',
- 'billing_email')
+ fields = ('cc_name', 'cc_number', 'cc_expiration', 'cc_cvv2')
View
6 satchless/contrib/payment/authorizenet_provider/models.py
@@ -2,14 +2,16 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
-class AuthorizeNetPayment(models.Model):
+from ....util.models import DeferredOneToOneField
+
+class AuthorizeNetPaymentBase(models.Model):
cc_name = models.CharField(_('Name on Credit Card'), max_length=128)
cc_number = models.CharField(_('Card Number'), max_length=32)
cc_expiration = models.DateField(_('Exp. date'), null=True)
cc_cvv2 = models.CharField(_('CVV2 Security Number'), max_length=4)
- billing_email = models.EmailField(_("Email Address"), blank=True)
response = models.ForeignKey(authorizenet.models.Response, null=True,
blank=True, editable=False)
+ order = DeferredOneToOneField('order', related_name='payment', editable=False)
class Meta:
abstract = True
View
8 satchless/delivery/__init__.py
@@ -35,6 +35,14 @@ def get_configuration_form(self, delivery_group, data, typ=None):
'''
return None
+ def get_configuration_forms_for_groups(self, delivery_groups, data):
+ delivery_group_forms = []
+ delivery_types = dict((dt.typ, dt) for dt in self.enum_types())
+ for group in delivery_groups:
+ form = self.get_configuration_form(group, data)
+ delivery_group_forms.append((group, delivery_types[group.delivery_type], form))
+ return delivery_group_forms
+
def save(self, delivery_group, form):
'''
Take a valid form instance if any and creates a DeliveryVariant instance.
View
5 satchless/delivery/tests.py
@@ -41,7 +41,7 @@ def get_configuration_form(self, delivery_group, data, typ=None):
typ = typ or delivery_group.delivery_type
try:
delivery_type = TestDeliveryType.objects.get(typ=typ)
- except TestDeliveryType.DoesNotExists:
+ except TestDeliveryType.DoesNotExist:
raise ValueError('Unable to find a delivery type: %s' %
(typ, ))
@@ -51,12 +51,13 @@ def get_configuration_form(self, delivery_group, data, typ=None):
return TestDeliveryDetailsForm(data or None, instance=instance)
def save(self, delivery_group, form=None, typ=None):
+ typ = typ or delivery_group.delivery_type
if form:
form.save()
else:
try:
delivery_type = TestDeliveryType.objects.get(typ=typ)
- except TestDeliveryType.DoesNotExists:
+ except TestDeliveryType.DoesNotExist:
raise ValueError('Unable to find a delivery type: %s' %
(typ, ))
TestDeliveryVariant.objects.create(
View
25 satchless/order/handler.py
@@ -1,6 +1,3 @@
-from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
-
from satchless.core.handler import QueueHandler
from ..delivery import DeliveryProvider, DeliveryType
@@ -11,22 +8,14 @@
class PartitionerQueue(Partitioner, QueueHandler):
element_class = Partitioner
- def partition(self, cart, items=None):
+ def partition(self, cart, items):
groups = []
remaining_items = items or list(cart.get_all_items())
for handler in self.queue:
handled_groups, remaining_items = handler.partition(cart,
remaining_items)
groups += handled_groups
- if remaining_items:
- raise ImproperlyConfigured('Unhandled items remaining in cart.')
- return groups
-
-
-partitioners = getattr(settings, 'SATCHLESS_ORDER_PARTITIONERS', [
- 'satchless.contrib.order.partitioner.simple.SimplePartitioner',
-])
-partitioner_queue = PartitionerQueue(*partitioners)
+ return groups, remaining_items
### PAYMENT PROVIDERS
@@ -48,7 +37,7 @@ def _get_provider(self, order, typ):
if payment_type.typ == typ:
return payment_type.provider
raise ValueError('Unable to find a payment provider for type %s' %
- (typ, ))
+ (typ,))
def get_configuration_form(self, order, data, typ=None):
typ = typ or order.payment_type
@@ -94,14 +83,6 @@ def get_configuration_form(self, delivery_group, data, typ=None):
return provider.get_configuration_form(delivery_group=delivery_group,
data=data, typ=typ)
- def get_configuration_forms_for_groups(self, delivery_groups, data):
- delivery_group_forms = []
- delivery_types = dict((dt.typ, dt) for dt in self.enum_types())
- for group in delivery_groups:
- form = self.get_configuration_form(group, data)
- delivery_group_forms.append((group, delivery_types[group.delivery_type], form))
- return delivery_group_forms
-
def save(self, delivery_group, form, typ=None):
typ = typ or delivery_group.delivery_type
provider = self._get_provider(delivery_group, typ)
View
15 satchless/order/models.py
@@ -12,10 +12,6 @@
class Order(models.Model):
- """
- Add this to your concrete model:
- cart = models.ForeignKey(Cart, related_name='orders')
- """
STATUS_CHOICES = (
('checkout', _('undergoing checkout')),
@@ -123,10 +119,6 @@ def is_empty(self):
class DeliveryGroup(models.Model):
- """
- add this to your concrete model:
- order = models.ForeignKey(Order, related_name='groups')
- """
order = DeferredForeignKey('order', related_name='groups', editable=False)
delivery_price = models.DecimalField(_('unit price'),
@@ -173,12 +165,13 @@ def get_total(self):
def add_item(self, variant, quantity, price, product_name=None):
product_name = product_name or unicode(variant)
- self.items.create(product_variant=variant, quantity=quantity,
- unit_price_net=price.net,
- unit_price_gross=price.gross)
+ return self.items.create(product_variant=variant, quantity=quantity,
+ unit_price_net=price.net, product_name=product_name,
+ unit_price_gross=price.gross)
class OrderedItem(models.Model):
+
delivery_group = DeferredForeignKey('delivery_group', related_name='items',
editable=False)
product_variant = DeferredForeignKey('variant', blank=True, null=True,
View
6 satchless/order/tests.py
@@ -47,7 +47,7 @@ class TestCheckoutApp(CheckoutApp):
Order = order_app.Order
Cart = cart_app.Cart
-checkout_app = TestCheckoutApp()
+checkout_app = TestCheckoutApp(cart_app=cart_app)
class OrderTest(ViewsTestCase):
@@ -95,7 +95,7 @@ def test_order_content_is_deleted_when_cart_content_changes(self):
order = checkout_app.Order.objects.create(cart=cart, user=cart.owner,
currency=cart.currency)
- order = checkout_app.partition_cart(cart, order)
+ checkout_app.partition_cart(cart, order)
cart.replace_item(self.macaw_blue_fake, Decimal('2.45'))
cart.replace_item(self.cockatoo_white_a, Decimal('2.45'))
@@ -110,7 +110,7 @@ def test_order_view(self):
order = checkout_app.Order.objects.create(cart=cart, user=cart.owner,
currency=cart.currency)
- order = checkout_app.partition_cart(cart, order)
+ checkout_app.partition_cart(cart, order)
self._test_GET_status(order_app.reverse('details',
args=(order.token,)))
View
15 satchless/pricing/app.py
@@ -0,0 +1,15 @@
+from django.conf import settings
+from .handler import PricingQueue
+
+class ProductAppPricingMixin(object):
+
+ def __init__(self, *args, **kwargs):
+ self.pricing_handler = kwargs.pop(
+ 'pricing_handler', PricingQueue(*getattr(settings,
+ 'SATCHLESS_PRICING_HANDLERS', ())))
+ super(ProductAppPricingMixin, self).__init__(*args, **kwargs)
+
+ def get_context_data(self, *args, **context):
+ return super(ProductAppPricingMixin, self).get_context_data(
+ *args, pricing_handler=self.pricing_handler, **context)
+
View
6 satchless/pricing/handler.py
@@ -1,5 +1,3 @@
-from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
from satchless.core.handler import QueueHandler
from . import PricingHandler
@@ -24,7 +22,3 @@ def get_product_price_range(self, product, currency=None, price_range=None, **co
return price_range
-if not getattr(settings, 'SATCHLESS_PRICING_HANDLERS', None):
- raise ImproperlyConfigured('You need to configure '
- 'SATCHLESS_PRICING_HANDLERS')
-pricing_queue = PricingQueue(*settings.SATCHLESS_PRICING_HANDLERS)
View
2  satchless/pricing/tests.py
@@ -108,3 +108,5 @@ def test_tax(self):
self.assertEqual(pr.max_price.gross, self.p2.gross*2)
self.assertEqual(pr.max_price.currency, self.p2.currency)
self.assertEqual(pr.max_price.tax_name, tax_name)
+
+
View
2  satchless/product/templates/satchless/product/view.html
@@ -22,7 +22,7 @@
{% for variant in product.variants.all %}
<li>
<strong>{{ variant }}</strong>
- {% variant_price variant currency='BTC' as price %}{% if price %}
+ {% variant_price variant pricing_handler currency='BTC' as price %}{% if price %}
price: {{ price.gross|floatformat:2 }} <span class="currency">BTC</span>
({{ price.net|floatformat:2 }} <span class="currency">BTC</span> net)
{% endif %}
View
26 satchless/product/templatetags/product_prices.py
@@ -7,8 +7,9 @@
register = template.Library()
class BasePriceNode(Node):
- def __init__(self, item, kwargs, asvar):
+ def __init__(self, item, pricing_handler, kwargs, asvar):
self.item = item
+ self.pricing_handler = pricing_handler
self.kwargs = kwargs
self.asvar = asvar
@@ -19,15 +20,14 @@ def get_currency_for_item(self, item):
return getattr(settings, 'SATCHLESS_DEFAULT_CURRENCY', None)
def render(self, context):
- from satchless.pricing.handler import pricing_queue
item = self.item.resolve(context)
kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context))
for k, v in self.kwargs.items()])
- self.pricing_handler = kwargs.pop('handler', pricing_queue)
currency = kwargs.pop('currency', self.get_currency_for_item(item))
+ pricing_handler = self.pricing_handler.resolve(context)
result = ''
if currency:
- r = self.get_price(item, currency, **kwargs)
+ r = self.get_price(item, pricing_handler, currency, **kwargs)
if r:
result = r
@@ -37,27 +37,27 @@ def render(self, context):
return result
class VariantPriceNode(BasePriceNode):
- def get_price(self, product, currency, **kwargs):
- return self.pricing_handler.get_variant_price(product, currency, **kwargs)
+ def get_price(self, product, pricing_handler, currency, **kwargs):
+ return pricing_handler.get_variant_price(product, currency, **kwargs)
class ProductPriceRangeNode(BasePriceNode):
- def get_price(self, product, currency, **kwargs):
- return self.pricing_handler.get_product_price_range(product, currency, **kwargs)
+ def get_price(self, product, pricing_handler, currency, **kwargs):
+ return pricing_handler.get_product_price_range(product, currency, **kwargs)
def parse_price_tag(parser, token):
bits = token.split_contents()
- if len(bits) < 3:
+ if len(bits) < 4:
raise template.TemplateSyntaxError(
"'%s' syntax is {%% %s <instance> [currency='<iso-code>'] as <variable-name> %%}" % (
bits[0], bits[0]))
- product = parser.compile_filter(bits[1])
+ item = parser.compile_filter(bits[1])
+ pricing_handler = parser.compile_filter(bits[2])
kwargs = {}
asvar = None
- bits = bits[2:]
+ bits = bits[3:]
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
-
if len(bits):
for bit in bits:
match = kwarg_re.match(bit)
@@ -68,7 +68,7 @@ def parse_price_tag(parser, token):
kwargs[name] = parser.compile_filter(value)
else:
raise template.TemplateSyntaxError("'%s' takes only named arguments" % bits[0])
- return product, kwargs, asvar
+ return item, pricing_handler, kwargs, asvar
@register.tag
def variant_price(parser, token):
View
39 satchless/product/tests/pricing.py
@@ -58,13 +58,13 @@ def get_product_price_range(self, *args, **kwargs):
class BasicHandlerTest(TestCase):
def setUp(self):
- self.pricing_queue = PricingQueue(FiveZlotyPriceHandler,
+ self.pricing_handler = PricingQueue(FiveZlotyPriceHandler,
NinetyPerecentTaxPriceHandler,
TenPercentDiscountPriceHandler)
def test_discounted_price(self):
- price = self.pricing_queue.get_variant_price(None, u'PLN', quantity=1,
- discount=True)
+ price = self.pricing_handler.get_variant_price(None, u'PLN', quantity=1,
+ discount=True)
self.assertEqual(price,
Price(net=5*decimal.Decimal('0.9'),
gross=(5 * decimal.Decimal('1.9') *
@@ -72,8 +72,8 @@ def test_discounted_price(self):
currency=u'PLN'))
def test_undiscounted_price(self):
- price = self.pricing_queue.get_variant_price(None, u'PLN', quantity=1,
- discount=False)
+ price = self.pricing_handler.get_variant_price(None, u'PLN', quantity=1,
+ discount=False)
self.assertEqual(price,
Price(net=5,
gross=5*decimal.Decimal('1.9'),
@@ -81,15 +81,15 @@ def test_undiscounted_price(self):
class PricingTagsTest(TestCase):
def setUp(self):
- self.pricing_queue = PricingQueue(FiveZlotyPriceHandler,
+ self.pricing_handler = PricingQueue(FiveZlotyPriceHandler,
NinetyPerecentTaxPriceHandler,
TenPercentDiscountPriceHandler)
def test_undiscounted_variant_price_tag_without_asvar(self):
token = mock.Mock()
token.split_contents.return_value = ('variant_price', 'product',
- 'currency=PLN', 'discount=0',
- 'handler=handler')
+ 'pricing_handler', 'currency=PLN',
+ 'discount=0')
parser = mock.Mock()
def side_effect(arg):
return template.Variable(arg)
@@ -99,10 +99,12 @@ def side_effect(arg):
#testing simple expresion
self.assertEqual(node.item.var, 'product')
+ self.assertEqual(node.pricing_handler.var, 'pricing_handler')
self.assertEqual(node.kwargs['currency'].var, 'PLN')
self.assertEqual(node.kwargs['discount'].var, '0')
- context = {'product': 'product', 'PLN': 'PLN', '0': 0, 'handler': self.pricing_queue}
+ context = {'product': 'product', 'PLN': 'PLN', '0': 0,
+ 'pricing_handler': self.pricing_handler}
result = node.render(context)
self.assertEqual(result,
Price(net=5,
@@ -112,8 +114,8 @@ def side_effect(arg):
def test_discounted_variant_price_tag_without_asvar(self):
token = mock.Mock()
token.split_contents.return_value = ('variant_price', 'product',
- 'currency=PLN',
- 'handler=handler')
+ 'pricing_handler',
+ 'currency=PLN')
parser = mock.Mock()
def side_effect(arg):
return template.Variable(arg)
@@ -123,9 +125,11 @@ def side_effect(arg):
#testing simple expresion
self.assertEqual(node.item.var, 'product')
+ self.assertEqual(node.pricing_handler.var, 'pricing_handler')
self.assertEqual(node.kwargs['currency'].var, 'PLN')
- context = {'product': 'product', 'PLN': 'PLN', '0': 0, 'handler': self.pricing_queue}
+ context = {'product': 'product', 'PLN': 'PLN', '0': 0,
+ 'pricing_handler': self.pricing_handler}
result = node.render(context)
self.assertEqual(result,
Price(net=5*decimal.Decimal('0.9'),
@@ -136,7 +140,7 @@ def side_effect(arg):
def test_variant_price_tag_with_asvar(self):
token = mock.Mock()
token.split_contents.return_value = ('variant_price', 'product',
- 'currency=PLN', 'handler=handler',
+ 'pricing_handler', 'currency=PLN',
'as', 'price')
parser = mock.Mock()
def side_effect(arg):
@@ -148,7 +152,8 @@ def side_effect(arg):
self.assertEqual(node.kwargs['currency'].var, 'PLN')
self.assertEqual(node.asvar, 'price')
- context = {'product': 'product', 'PLN': 'PLN', '0': 0, 'handler': self.pricing_queue}
+ context = {'product': 'product', 'PLN': 'PLN', '0': 0,
+ 'pricing_handler': self.pricing_handler}
node.render(context)
self.assertEqual(context['price'],
Price(net=5*decimal.Decimal('0.9'),
@@ -159,8 +164,9 @@ def side_effect(arg):
def test_undiscounted_product_price_range(self):
token = mock.Mock()
token.split_contents.return_value = ('product_price_range', 'product',
+ 'pricing_handler',
'currency=PLN', 'discount=0',
- 'handler=handler', 'as','price')
+ 'as','price')
parser = mock.Mock()
def side_effect(arg):
return template.Variable(arg)
@@ -168,7 +174,8 @@ def side_effect(arg):
node = product_prices.product_price_range(parser, token)
- context = {'product': 'product', 'PLN': 'PLN', '0': 0, 'handler': self.pricing_queue}
+ context = {'product': 'product', 'PLN': 'PLN', '0': 0,
+ 'pricing_handler': self.pricing_handler}
node.render(context)
self.assertEqual(context['price'].min_price,
Price(net=5, gross=5*decimal.Decimal('1.9'),
View
5 satchless/util/models.py
@@ -94,6 +94,11 @@ class DeferredForeignKey(DeferredField):
klass = models.ForeignKey
+class DeferredOneToOneField(DeferredField):
+
+ klass = models.OneToOneField
+
+
class DeferredManyToManyField(DeferredField):
klass = models.ManyToManyField

No commit comments for this range

Something went wrong with that request. Please try again.