Commits

Bruce Kroeze committed e24caff

splitting out shipping modules into directories, like with payment modules.

Comments (0)

Files changed (30)

satchmo/contact/models.py

 from decimal import Decimal
 from django.conf import settings
 from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.core import urlresolvers
 from django.db import models
+from django.db.models import permalink
+from django.dispatch import dispatcher
 from django.utils.translation import ugettext_lazy as _
+from satchmo import tax
 from satchmo.configuration import config_choice_values, config_value, SettingNotSet
 from satchmo.discount.models import Discount
 from satchmo.payment.config import payment_choices
 from satchmo.product.models import Product, DownloadableProduct
 from satchmo.shop.templatetags.satchmo_currency import moneyfmt
 from satchmo.shop.utils import load_module
-from satchmo import tax
+from signals import order_success
 import config
 import datetime
 import logging
 import operator
 import satchmo.shipping.config
 import sys
-from django.db.models import permalink
-from django.core import urlresolvers
-from django.contrib.sites.models import Site
 
 try:
     from django.utils.safestring import mark_safe
             subtype = orderitem.product.get_subtype_with_attr('order_success')
             if subtype:
                 subtype.order_success(self, orderitem)
+        dispatcher.send(signal=order_success, sender=self.__class__, instance=self)
+        
                 
     def _paid_in_full(self):
         """True if total has been paid"""

satchmo/contact/signals.py

+"""Signals sent by the Cart system"""
+order_success=object()

satchmo/payment/common/forms.py

 from django.template import Context
 from django.template import loader
 from django.utils.translation import ugettext as _
-from satchmo.configuration import config_value
 from satchmo.contact.forms import ContactInfoForm
 from satchmo.contact.models import Contact
 from satchmo.discount.models import Discount
 from satchmo.payment.config import payment_choices
-from satchmo.payment.urls import lookup_template
+from satchmo.shipping.config import shipping_methods
 from satchmo.shop.models import Cart
-from satchmo.shop.utils import load_module
+from satchmo.shop.utils.dynamic import lookup_template
 from satchmo.shop.views.utils import CreditCard
 import calendar
 import datetime
     shipping_options = []
     shipping_dict = {}
     
-    for module in config_value('SHIPPING','MODULES'):
-        #Create the list of information the user will see
-        shipping_module = load_module(module)
-        shipping_instance = shipping_module.Calc(cart, contact)
-        if shipping_instance.valid():
+    for method in shipping_methods():
+        method.calculate(cart, contact)
+        if method.valid():
             template = lookup_template(paymentmodule, 'shipping_options.html')
             t = loader.get_template(template)
-            shipcost = shipping_instance.cost()
+            shipcost = method.cost()
             c = Context({
                 'amount': shipcost,
-                'description' : shipping_instance.description(),
-                'method' : shipping_instance.method(),
-                'expected_delivery' : shipping_instance.expectedDelivery() })
-            shipping_options.append((shipping_instance.id, t.render(c)))
-            shipping_dict[shipping_instance.id] = shipcost
+                'description' : method.description(),
+                'method' : method.method(),
+                'expected_delivery' : method.expectedDelivery() })
+            shipping_options.append((method.id, t.render(c)))
+            shipping_dict[method.id] = shipcost
     
     return shipping_options, shipping_dict
  

satchmo/payment/common/pay_ship.py

-from django.template import loader, Context
-from django.utils.translation import ugettext as _
 from decimal import Decimal
 from django.conf import settings
 from django.core.mail import send_mail
+from django.template import loader, Context
+from django.utils.translation import ugettext as _
 from satchmo.configuration import config_value
 from satchmo.contact.models import OrderItem, OrderItemDetail
+from satchmo.product.models import CustomTextField
+from satchmo.shipping.config import shipping_method_by_key
 from satchmo.shop.models import Config
 from satchmo.shop.utils import load_module
 from socket import error as SocketError
-from satchmo.product.models import CustomTextField
 import logging
 
 log = logging.getLogger('pay_ship')
     new_order.shipping_cost = Decimal("0.00")
 
     # Save the shipping info
-    for module in config_value('SHIPPING','MODULES'):
-        shipping_module = load_module(module)
-        shipping_instance = shipping_module.Calc(cart, contact)
-        if shipping_instance.id == shipping:
-            new_order.shipping_description = shipping_instance.description().encode()
-            new_order.shipping_method = shipping_instance.method()
-            new_order.shipping_cost = shipping_instance.cost()
-            new_order.shipping_model = shipping
+    shipper = shipping_method_by_key(shipping)
+    shipper.calculate(cart, contact)
+    new_order.shipping_description = shipper.description().encode()
+    new_order.shipping_method = shipper.method()
+    new_order.shipping_cost = shipper.cost()
+    new_order.shipping_model = shipping
 
     # Temp setting of the tax and total so we can save it
     new_order.total = Decimal('0.00')
         else:
             log.fatal('Error sending mail: %s' % e)
             raise IOError('Could not send email, please check to make sure your email settings are correct, and that you are not being blocked by your ISP.')    
-
-
-   
-
-    
-
-

satchmo/payment/common/views/common_contact.py

 from satchmo.contact.common import get_area_country_options
 from satchmo.contact.models import Contact
 from satchmo.payment.common.forms import PaymentContactInfoForm
-from satchmo.payment.urls import lookup_url
 from satchmo.shop.models import Cart
+from satchmo.shop.utils.dynamic import lookup_url
 
 def contact_info(request):
     """View which collects demographic information from customer."""

satchmo/payment/common/views/confirm.py

 from django.utils.translation import ugettext as _
 from satchmo.contact.models import Order
 from satchmo.payment.common.pay_ship import send_order_confirmation
-from satchmo.payment.urls import lookup_url, lookup_template
+from satchmo.shop.utils.dynamic import lookup_url, lookup_template
 from satchmo.shop.models import Cart
 import logging
 

satchmo/payment/common/views/payship.py

 from satchmo.payment.common.pay_ship import pay_ship_save
 from satchmo.payment.config import payment_live
 from satchmo.payment.models import CreditCardDetail
-from satchmo.payment.urls import lookup_url, lookup_template
+from satchmo.shop.utils.dynamic import lookup_url, lookup_template
 from satchmo.shop.models import Cart
 
 selection = _("Please Select")

satchmo/payment/modules/autosuccess/views.py

 from satchmo.configuration import config_get_group
 from satchmo.contact.models import Order, Contact, OrderPayment
 from satchmo.payment.common.pay_ship import pay_ship_save, send_order_confirmation
-from satchmo.payment.urls import lookup_url, lookup_template
+from satchmo.shop.utils.dynamic import lookup_url, lookup_template
 from satchmo.shop.models import Cart
 
 import logging
     newOrder.add_status(status='Pending', notes = "Order successfully submitted")
 
     orderpayment = OrderPayment(order=newOrder, amount=newOrder.balance, payment=payment_module.KEY.value)
-    orderpayment.save()
+    orderpayment.save()        
 
     #Now, send a confirmation email
     if payment_module['EMAIL'].value:

satchmo/payment/modules/google/views.py

 from satchmo.contact.models import Order
 from satchmo.payment.common.views import payship
 from satchmo.payment.config import payment_live
-from satchmo.payment.urls import lookup_url, lookup_template
+from satchmo.shop.utils.dynamic import lookup_url, lookup_template
 from satchmo.shop.models import Cart
 import base64
 import hmac

satchmo/payment/modules/paypal/views.py

 from satchmo.contact.models import Order
 from satchmo.payment.common.views import payship
 from satchmo.payment.config import payment_live
-from satchmo.payment.urls import lookup_url, lookup_template
 from satchmo.shop.models import Cart
+from satchmo.shop.utils.dynamic import lookup_url, lookup_template
 
 import logging
 

satchmo/payment/tests.py

 from django.test import TestCase
 #from models import GiftCertificate
 from satchmo.configuration import config_get_group, config_value
-from urls import lookup_template, lookup_url, make_urlpatterns
+from satchmo.shop.utils.dynamic import lookup_template, lookup_url
+from urls import make_urlpatterns
 #from modules.giftcertificate.utils import generate_certificate_code, generate_code
 
 alphabet = 'abcdefghijklmnopqrstuvwxyz'

satchmo/payment/urls.py

 from django.conf.urls.defaults import *
-from django.contrib.sites.models import Site
-from django.core import urlresolvers
 from satchmo.configuration import config_get_group, config_get, config_value
-from satchmo.shop.utils import load_module, url_join
 import logging
 
 log = logging.getLogger('payment.urls')
     
 urlpatterns += make_urlpatterns()
 
-# --- Helper functions ---
-
-def lookup_template(settings, template):
-    """Return a template name, which may have been overridden in the settings."""
-
-    if settings.has_key('TEMPLATE_OVERRIDES'):
-        val = settings['TEMPLATE_OVERRIDES']
-        template = val.get(template, template)
-
-    return template
-
-def lookup_url(settings, name, include_server=False, ssl=False):
-    """Look up a named URL for the payment module.
-
-    Tries a specific-to-general lookup fallback, returning
-    the first positive hit.
-
-    First look for a dictionary named "URL_OVERRIDES" on the settings object.
-    Next try prepending the module name to the name
-    Last just look up the name
-    """
-    url = None
-
-    if settings.has_key('URL_OVERRIDES'):
-        val = settings['URL_OVERRIDES']
-        url = val.get(name, None)
-
-    if not url:
-        try:
-            url = urlresolvers.reverse(settings.KEY.value + "_" + name)
-        except urlresolvers.NoReverseMatch:
-            pass
-
-    if not url:
-        url = urlresolvers.reverse(name)
-
-    if include_server:
-        if ssl:
-            method = "https://"
-        else:
-            method = "http://"
-        site = Site.objects.get_current()
-        url = url_join(method, site.domain, url)
-
-    return url

satchmo/payment/views.py

 from satchmo.contact.models import Order, OrderItem
 from satchmo.payment.common.forms import PaymentMethodForm
 from satchmo.payment.common.views import common_contact
-from satchmo.payment.urls import lookup_url
+from satchmo.shop.utils.dynamic import lookup_url
 from satchmo.shop.views.utils import bad_or_missing
 import django.newforms as forms
 import logging

satchmo/shipping/config.py

     description=_("Active shipping modules"),
     help_text=_("Select the active shipping modules, save and reload to set any module-specific shipping settings."),
     default=["satchmo.shipping.modules.per"],
-    choices=[('satchmo.shipping.modules.flat', _('Flat rate')),
-             ('satchmo.shipping.modules.per', _('Per piece'))]
+    choices=[('satchmo.shipping.modules.per', _('Per piece'))]
     ))
     
-config_register([
+# --- Load default shipping modules.  Ignore import errors, user may have deleted them. ---
+_default_modules = ('dummy', 'flat', 'per')
 
-DecimalValue(SHIPPING_GROUP,
-    'FLAT_RATE',
-    description=_("Flat shipping"),
-    requires=SHIPPING_ACTIVE,
-    requiresvalue='satchmo.shipping.modules.flat',
-    default="4.00"),
+for module in _default_modules:
+    try:
+        load_module("satchmo.shipping.modules.%s.config" % module)
+    except ImportError:
+        log.debug('Could not load default shipping module configuration: %s', module)
 
-StringValue(SHIPPING_GROUP,
-    'FLAT_SERVICE',
-    description=_("Flat Shipping Service"),
-    help_text=_("Shipping service used with Flat rate shipping"),
-    requires=SHIPPING_ACTIVE,
-    requiresvalue='satchmo.shipping.modules.flat',
-    default=u"U.S. Mail"),
-    
-StringValue(SHIPPING_GROUP,
-    'FLAT_DAYS',
-    description=_("Flat Delivery Days"),
-    requires=SHIPPING_ACTIVE,
-    requiresvalue='satchmo.shipping.modules.flat',
-    default="3 - 4 business days"),
+# --- Load any extra shipping modules. ---
+extra_shipping = getattr(settings, 'CUSTOM_SHIPPING_MODULES', ())
 
-DecimalValue(SHIPPING_GROUP,
-    'PER_RATE',
-    description=_("Per item price"),
-    requires=SHIPPING_ACTIVE,
-    requiresvalue='satchmo.shipping.modules.per',
-    default="4.00"),
-
-StringValue(SHIPPING_GROUP,
-    'PER_SERVICE',
-    description=_("Per Item Shipping Service"),
-    help_text=_("Shipping service used with per item shipping"),
-    requires=SHIPPING_ACTIVE,
-    requiresvalue='satchmo.shipping.modules.per',
-    default=u"U.S. Mail"),
-
-StringValue(SHIPPING_GROUP,
-    'PER_DAYS',
-    description=_("Per Item Delivery Days"),
-    requires=SHIPPING_ACTIVE,
-    requiresvalue='satchmo.shipping.modules.per',
-    default="3 - 4 business days")
-
-])
-
-# --- Load any extra payment modules. ---
-extra_payment = getattr(settings, 'CUSTOM_SHIPPING_MODULES', ())
-
-for extra in extra_payment:
+for extra in extra_shipping:
     try:
         load_module("%s.config" % extra)
     except ImportError:
-        log.warn('Could not load payment module configuration: %s' % extra)
+        log.warn('Could not load shipping module configuration: %s' % extra)
+
+class ShippingModuleNotFound(Exception):
+    def __init__(key):
+        self.key = key
+
+def shipping_methods():
+    methods = []
+    for m in config_value('SHIPPING', 'MODULES'):
+        module = load_module(m)
+        methods.extend(module.get_methods())
+    return methods
+    
+def shipping_method_by_key(key):
+    for method in shipping_methods():
+        if method.id == key:
+            return method
+    raise ShippingModuleNotFound(key)
+
+def shipping_choices():
+    choices = []
+    keys = []
+    for method in shipping_methods():
+        key = method.id
+        label = method.description()
+        choices.append((key, label))
+    return choices

satchmo/shipping/modules/base.py

+class BaseShipper(object):
+    def __init__(self, cart=None, contact=None):
+        self._calculated = False
+        if cart or contact:
+            self.calculate(cart, contact)
+        
+    def calculate(self, cart, contact):
+        """
+        Perform shipping calculations, separated from __init__ so that the object can be 
+        used for keys and labels more easily.
+        """
+        self.cart = cart
+        self.contact = contact
+        self._calculated = True

satchmo/shipping/modules/dummy.py

-"""
-This dummy module can be used as a basis for creating your own
-
-- Copy this file to a new name
-- Make the changes described below
-"""
-
-# Note, make sure you use decimal math everywhere!
-from decimal import Decimal
-from django.utils.translation import ugettext as _
-
-class Calc(object):
-    #Define some constants here
-    #The most important is that id is unique
-    flatRateFee = Decimal("15.00")
-    id = "Dumy"
-
-    def __init__(self, cart, contact):
-        # We're copying in the cart and contact info because we'll probably use
-        # it later.
-
-        self.cart = cart
-        self.contact = contact
-
-    def __str__(self):
-        """
-        This is mainly helpful for debugging purposes
-        """
-        return "Dummy Flat Rate"
-
-    def description(self):
-        """
-        A basic description that will be displayed to the user when selecting their shipping options
-        """
-        return _("Dummy Flat Rate Shipping")
-
-    def cost(self):
-        """
-        Complex calculations can be done here as long as the return value is a decimal figure
-        """
-        return(self.flatRateFee)
-
-    def method(self):
-        """
-        Describes the actual delivery service (Mail, FedEx, DHL, UPS, etc)
-        """
-        return _("US Mail")
-
-    def expectedDelivery(self):
-        """
-        Can be a plain string or complex calcuation returning an actual date
-        """
-        return _("3 - 4 business days")
-
-    def valid(self, order=None):
-        """
-        Can do complex validation about whether or not this option is valid.
-        For example, may check to see if the recipient is in an allowed country
-        or location.
-        """
-        return True
-

satchmo/shipping/modules/dummy/__init__.py

+import shipper
+
+def get_methods():
+    return [shipper.Shipper()]
+

satchmo/shipping/modules/dummy/config.py

+# from satchmo.configuration import *
+# 
+# SHIP_MODULES = config_get('SHIPPING', 'MODULES')
+# SHIP_MODULES.add_choice(('satchmo.shipping.modules.dummy', 'Dummy Shipping'))
+# SHIPPING_GROUP = config_get_group('SHIPPING')
+# 
+# config_register([
+# DecimalValue(SHIPPING_GROUP,
+#     'FLAT_RATE',
+#     description=_("Flat shipping"),
+#     requires=SHIPPING_ACTIVE,
+#     requiresvalue='satchmo.shipping.modules.flat',
+#     default="4.00"),
+# ])

satchmo/shipping/modules/dummy/shipper.py

+"""
+This dummy module can be used as a basis for creating your own
+
+- Copy this module to a new name
+- Make the changes described below
+"""
+
+# Note, make sure you use decimal math everywhere!
+from decimal import Decimal
+from django.utils.translation import ugettext as _
+from satchmo.shipping.modules.base import BaseShipper
+
+class Shipper(BaseShipper):
+
+    flatRateFee = Decimal("15.00")
+    id = "Dumy"
+        
+    def __str__(self):
+        """
+        This is mainly helpful for debugging purposes
+        """
+        return "Dummy Flat Rate"
+        
+    def description(self):
+        """
+        A basic description that will be displayed to the user when selecting their shipping options
+        """
+        return _("Dummy Flat Rate Shipping")
+
+    def cost(self):
+        """
+        Complex calculations can be done here as long as the return value is a decimal figure
+        """
+        assert(self._calculated)
+        return(self.flatRateFee)
+
+    def method(self):
+        """
+        Describes the actual delivery service (Mail, FedEx, DHL, UPS, etc)
+        """
+        return _("US Mail")
+
+    def expectedDelivery(self):
+        """
+        Can be a plain string or complex calcuation returning an actual date
+        """
+        return _("3 - 4 business days")
+
+    def valid(self, order=None):
+        """
+        Can do complex validation about whether or not this option is valid.
+        For example, may check to see if the recipient is in an allowed country
+        or location.
+        """
+        return True
+

satchmo/shipping/modules/flat.py

-"""
-Each shipping option uses the data in an Order object to calculate the shipping cost and return the value
-"""
-from decimal import Decimal
-from django.utils.translation import ugettext, ugettext_lazy
-from satchmo.configuration import config_value
-_ = ugettext_lazy
-
-class Calc(object):
-
-    id = "FlatRate"
-
-    def __init__(self, cart, contact):
-        self.cart = cart
-        self.contact = contact
-
-    def __str__(self):
-        """
-        This is mainly helpful for debugging purposes
-        """
-        return "Flat Rate"
-
-    def description(self):
-        """
-        A basic description that will be displayed to the user when selecting their shipping options
-        """
-        return _("Flat Rate Shipping")
-
-    def cost(self):
-        """
-        Complex calculations can be done here as long as the return value is a dollar figure
-        """
-        for cartitem in self.cart.cartitem_set.all():
-            if cartitem.product.is_shippable:
-                return config_value('SHIPPING', 'FLAT_RATE')
-        return Decimal("0.00")
-
-    def method(self):
-        """
-        Describes the actual delivery service (Mail, FedEx, DHL, UPS, etc)
-        """
-        return ugettext(config_value('SHIPPING', 'FLAT_SERVICE'))
-
-    def expectedDelivery(self):
-        """
-        Can be a plain string or complex calcuation returning an actual date
-        """
-        return ugettext(config_value('SHIPPING', 'FLAT_DAYS'))
-
-    def valid(self, order=None):
-        """
-        Can do complex validation about whether or not this option is valid.
-        For example, may check to see if the recipient is in an allowed country
-        or location.
-        """
-        return True
-

satchmo/shipping/modules/flat/__init__.py

+import shipper
+
+def get_methods():
+    return [shipper.Shipper()]
+

satchmo/shipping/modules/flat/config.py

+from django.utils.translation import ugettext_lazy as _
+from satchmo.configuration import *
+
+SHIP_MODULES = config_get('SHIPPING', 'MODULES')
+SHIP_MODULES.add_choice(('satchmo.shipping.modules.flat', _('Flat rate')))
+SHIPPING_GROUP = config_get_group('SHIPPING')
+
+config_register([
+
+DecimalValue(SHIPPING_GROUP,
+    'FLAT_RATE',
+    description=_("Flat shipping"),
+    requires=SHIP_MODULES,
+    requiresvalue='satchmo.shipping.modules.flat',
+    default="4.00"),
+
+StringValue(SHIPPING_GROUP,
+    'FLAT_SERVICE',
+    description=_("Flat Shipping Service"),
+    help_text=_("Shipping service used with Flat rate shipping"),
+    requires=SHIP_MODULES,
+    requiresvalue='satchmo.shipping.modules.flat',
+    default=u"U.S. Mail"),
+    
+StringValue(SHIPPING_GROUP,
+    'FLAT_DAYS',
+    description=_("Flat Delivery Days"),
+    requires=SHIP_MODULES,
+    requiresvalue='satchmo.shipping.modules.flat',
+    default="3 - 4 business days"),
+])

satchmo/shipping/modules/flat/shipper.py

+"""
+Each shipping option uses the data in an Order object to calculate the shipping cost and return the value
+"""
+from decimal import Decimal
+from django.utils.translation import ugettext, ugettext_lazy
+from satchmo.configuration import config_value
+_ = ugettext_lazy
+from satchmo.shipping.modules.base import BaseShipper
+
+class Shipper(BaseShipper):
+    id = "FlatRate"
+
+    def __str__(self):
+        """
+        This is mainly helpful for debugging purposes
+        """
+        return "Flat Rate: %s" % config_value('SHIPPING', 'FLAT_RATE')
+
+    def description(self):
+        """
+        A basic description that will be displayed to the user when selecting their shipping options
+        """
+        return _("Flat Rate Shipping")
+
+    def cost(self):
+        """
+        Complex calculations can be done here as long as the return value is a dollar figure
+        """
+        assert(self._calculated)
+        for cartitem in self.cart.cartitem_set.all():
+            if cartitem.product.is_shippable:
+                return config_value('SHIPPING', 'FLAT_RATE')
+        return Decimal("0.00")
+
+    def method(self):
+        """
+        Describes the actual delivery service (Mail, FedEx, DHL, UPS, etc)
+        """
+        return ugettext(config_value('SHIPPING', 'FLAT_SERVICE'))
+
+    def expectedDelivery(self):
+        """
+        Can be a plain string or complex calcuation returning an actual date
+        """
+        return ugettext(config_value('SHIPPING', 'FLAT_DAYS'))
+
+    def valid(self, order=None):
+        """
+        Can do complex validation about whether or not this option is valid.
+        For example, may check to see if the recipient is in an allowed country
+        or location.
+        """
+        return True
+

satchmo/shipping/modules/per.py

-"""
-Each shipping option uses the data in an Order object to calculate the shipping cost and return the value
-"""
-from decimal import Decimal
-from django.utils.translation import ugettext, ugettext_lazy
-from satchmo.configuration import config_value
-_ = ugettext_lazy
-
-class Calc(object):
-
-    id = "PerItem"
-
-    def __init__(self, cart, contact):
-        self.cart = cart
-        self.contact = contact
-
-    def __str__(self):
-        """
-        This is mainly helpful for debugging purposes
-        """
-        return "Per Item"
-
-    def description(self):
-        """
-        A basic description that will be displayed to the user when selecting their shipping options
-        """
-        return _("Per Item shipping")
-
-    def cost(self):
-        """
-        Complex calculations can be done here as long as the return value is a dollar figure
-        """
-        fee = Decimal("0.00")
-        rate = config_value('SHIPPING', 'PER_RATE')
-        for cartitem in self.cart.cartitem_set.all():
-            if cartitem.product.is_shippable:
-                fee += rate * cartitem.quantity
-        return fee
-
-    def method(self):
-        """
-        Describes the actual delivery service (Mail, FedEx, DHL, UPS, etc)
-        """
-        return ugettext(config_value('SHIPPING', 'PER_SERVICE'))
-
-    def expectedDelivery(self):
-        """
-        Can be a plain string or complex calcuation returning an actual date
-        """
-        return ugettext(config_value('SHIPPING', 'PER_DAYS'))
-
-    def valid(self, order=None):
-        """
-        Can do complex validation about whether or not this option is valid.
-        For example, may check to see if the recipient is in an allowed country
-        or location.
-        """
-        return True
-

satchmo/shipping/modules/per/__init__.py

+import shipper
+
+def get_methods():
+    return [shipper.Shipper()]
+

satchmo/shipping/modules/per/config.py

+from django.utils.translation import ugettext_lazy as _
+from satchmo.configuration import *
+
+SHIP_MODULES = config_get('SHIPPING', 'MODULES')
+
+# No need to add the choice, since it is in by default
+# SHIP_MODULES.add_choice(('satchmo.shipping.modules.per', _('Per piece')))
+
+SHIPPING_GROUP = config_get_group('SHIPPING')
+
+config_register([
+DecimalValue(SHIPPING_GROUP,
+    'PER_RATE',
+    description=_("Per item price"),
+    requires=SHIP_MODULES,
+    requiresvalue='satchmo.shipping.modules.per',
+    default="4.00"),
+
+StringValue(SHIPPING_GROUP,
+    'PER_SERVICE',
+    description=_("Per Item Shipping Service"),
+    help_text=_("Shipping service used with per item shipping"),
+    requires=SHIP_MODULES,
+    requiresvalue='satchmo.shipping.modules.per',
+    default=u"U.S. Mail"),
+
+StringValue(SHIPPING_GROUP,
+    'PER_DAYS',
+    description=_("Per Item Delivery Days"),
+    requires=SHIP_MODULES,
+    requiresvalue='satchmo.shipping.modules.per',
+    default="3 - 4 business days")
+])

satchmo/shipping/modules/per/shipper.py

+"""
+Each shipping option uses the data in an Order object to calculate the shipping cost and return the value
+"""
+from decimal import Decimal
+from django.utils.translation import ugettext, ugettext_lazy
+from satchmo.configuration import config_value
+_ = ugettext_lazy
+from satchmo.shipping.modules.base import BaseShipper
+
+class Shipper(BaseShipper):
+    id = "PerItem"
+
+    def __str__(self):
+        """
+        This is mainly helpful for debugging purposes
+        """
+        return "Per Item: %s" % config_value('SHIPPING', 'PER_RATE')
+
+    def description(self):
+        """
+        A basic description that will be displayed to the user when selecting their shipping options
+        """
+        return _("Per Item shipping")
+
+    def cost(self):
+        """
+        Complex calculations can be done here as long as the return value is a dollar figure
+        """
+        fee = Decimal("0.00")
+        rate = config_value('SHIPPING', 'PER_RATE')
+        for cartitem in self.cart.cartitem_set.all():
+            if cartitem.product.is_shippable:
+                fee += rate * cartitem.quantity
+        return fee
+
+    def method(self):
+        """
+        Describes the actual delivery service (Mail, FedEx, DHL, UPS, etc)
+        """
+        return ugettext(config_value('SHIPPING', 'PER_SERVICE'))
+
+    def expectedDelivery(self):
+        """
+        Can be a plain string or complex calcuation returning an actual date
+        """
+        return ugettext(config_value('SHIPPING', 'PER_DAYS'))
+
+    def valid(self, order=None):
+        """
+        Can do complex validation about whether or not this option is valid.
+        For example, may check to see if the recipient is in an allowed country
+        or location.
+        """
+        return True
+

satchmo/shipping/tests.py

 r"""
 >>> from django.db import models
->>> from satchmo.shipping.modules import flat, per
+>>> from satchmo.shipping.modules.flat.shipper import Shipper as flat
+>>> from satchmo.shipping.modules.per.shipper import Shipper as per
 >>> from satchmo.product.models import *
 >>> from satchmo.shop.models import *
 
 True
 >>> cart1.is_shippable
 True
->>> flat.Calc(cart1, None).cost()
+>>> flat(cart1, None).cost()
 Decimal("4.00")
->>> per.Calc(cart1, None).cost()
+>>> per(cart1, None).cost()
 Decimal("12.00")
 
 # TODO: Enable this test when DownloadableProduct is enabled
 #False
 #>>> cart1.is_shippable
 #False
-#>>> flat.Calc(cart1, None).cost()
+#>>> flat(cart1, None).cost()
 #Decimal("0.00")
-#>>> per.Calc(cart1, None).cost()
+#>>> per(cart1, None).cost()
 #Decimal("0.00")
 """
 

satchmo/shop/tests.py

         response = self.client.post(prefix+'/product/DJ-Rocks/prices/', {"1" : "S",
                                                       "2" : "B",
                                                       "quantity" : 1})
-        currency = config_get('SHOP', 'CURRENCY')
-        currency.update('C')
-        self.assertEquals('C', config_value('SHOP', 'CURRENCY'))
-        self.assertEquals(response.content, u'["DJ-Rocks_S_B", "C20.00"]')
+        content = response.content.split(',')
+        self.assertEquals(content[0], '["DJ-Rocks_S_B"')
+        self.assert_(content[1].endswith('20.00"]'))
 
         # This tests the option price_change feature, and again the productname
         response = self.client.post(prefix+'/product/DJ-Rocks/prices/', {"1" : "L",
                                                       "2" : "BL",
                                                       "quantity" : 2})
-        self.assertEquals(response.content, u'["DJ-Rocks_L_BL", "C23.00"]')
+        content = response.content.split(',')
+        self.assertEqual(content[0], '["DJ-Rocks_L_BL"')
+        self.assert_(content[1].endswith('23.00"]'))
 
 #        response = self.client.get(prefix+'/product/neat-software/')
 #        self.assertContains(response, "Neat Software", count=1, status_code=200)

satchmo/shop/utils/dynamic.py

+from django.core import urlresolvers
+from satchmo.shop.utils import url_join
+from django.contrib.sites.models import Site
+
+def lookup_template(settings, template):
+    """Return a template name, which may have been overridden in the settings."""
+
+    if settings.has_key('TEMPLATE_OVERRIDES'):
+        val = settings['TEMPLATE_OVERRIDES']
+        template = val.get(template, template)
+
+    return template
+
+def lookup_url(settings, name, include_server=False, ssl=False):
+    """Look up a named URL for the payment module.
+
+    Tries a specific-to-general lookup fallback, returning
+    the first positive hit.
+
+    First look for a dictionary named "URL_OVERRIDES" on the settings object.
+    Next try prepending the module name to the name
+    Last just look up the name
+    """
+    url = None
+
+    if settings.has_key('URL_OVERRIDES'):
+        val = settings['URL_OVERRIDES']
+        url = val.get(name, None)
+
+    if not url:
+        try:
+            url = urlresolvers.reverse(settings.KEY.value + "_" + name)
+        except urlresolvers.NoReverseMatch:
+            pass
+
+    if not url:
+        url = urlresolvers.reverse(name)
+
+    if include_server:
+        if ssl:
+            method = "https://"
+        else:
+            method = "http://"
+        site = Site.objects.get_current()
+        url = url_join(method, site.domain, url)
+
+    return url