Ensure product class is available in template

Without this change, it's not available when creating a new product.
Prevent attribute validation for parent products

This is a messy situation caused by Oscar's product update view trying
to handle too many different scenarios.  This fix is quite hacky and not
entirely watertight - a better fix will arrive as part of #693 when the
catalogue dashboard gets rewritten to have separate views and forms for
group and standalong products.

The core problem comes from trying to validate required attributes.
When you are creating a parent product that belongs to a product class
with required attributes, you don't want to enforce that the attributes
are set (eg the parent doesn't have a size).  Before this change, the
dashboard would not allow a parent product to be saved if it didn't have
the product class attributes set.

Hence we need to change the validation rules when the form is submitted,
but it actually impossible to tell exactly if the submitted data is
for a new group product or a standalone product with no stockrecord.  To
address this, a new is_group field has been added to the form to
allow the validation rules to be altered when entering a new group
product.  Also, the product's save method allows a new kwarg that
disables attribute validation - this is required when saving a parent
product.
Adjust product update template

- Ensure variant overview shows attributes
- Use form field partial where appropriate
Updates setup.py to allow django 1.6.x
Incompatability with Django 1.6, commit_on_success and sqlite3

Changes to the transaction handling in Django 1.6 resulted in
the ``commit_on_success`` decorating failing with SQLite3.

The `atomic` decorating is used instead when available.
Extend price object to specify price to use for offers

This removes the need for the "applies_to_tax_exclusive_prices" field on
the offer model.  Now the strategy can specify which price to use for
offers.

This change also introduces a sample UK strategy which specifies that
offers apply to tax-inclusive prices.
Remove applies_to_tax_exclusive_prices field

It's no longer necessary now that the price object can specify which
price to use for offers.
Ensure discounts are correctly applied to shipping methods without tax

We need to wrap the shipping method in a different discount wrapper
depending on whether the shipping method is aware of taxes.
diff --git a/oscar/apps/dashboard/vouchers/forms.py b/oscar/apps/dashboard/vouchers/forms.py
index e648543..ef4fadb 100644
--- a/oscar/apps/dashboard/vouchers/forms.py
+++ b/oscar/apps/dashboard/vouchers/forms.py
@@ -18,9 +18,9 @@ class VoucherForm(forms.Form):
     code = forms.CharField(label=_("Code"))

     start_date = forms.DateField(
-        label=_("Start date"), widget=widgets.DatePickerInput())
+        label=_("Start date"), widget=widgets.DatePickerInput)
     end_date = forms.DateField(
-        label=_("End date"), widget=widgets.DatePickerInput())
+        label=_("End date"), widget=widgets.DatePickerInput)
     usage = forms.ChoiceField(choices=Voucher.USAGE_CHOICES, label=_("Usage"))

     benefit_range = forms.ModelChoiceField(
diff --git a/oscar/apps/offer/models.py b/oscar/apps/offer/models.py
index 479d4ba..10a3770 100644
--- a/oscar/apps/offer/models.py
+++ b/oscar/apps/offer/models.py
@@ -1519,4 +1519,5 @@ class ShippingPercentageDiscountBenefit(ShippingBenefit):
         verbose_name_plural = _("Shipping percentage discount benefits")

     def shipping_discount(self, charge):
-        return charge * self.value / D('100.0')
+        discount = charge * self.value / D('100.0')
+        return discount.quantize(D('0.01'))
diff --git a/oscar/apps/promotions/context_processors.py b/oscar/apps/promotions/context_processors.py
index 151c256..1ec9dc4 100644
--- a/oscar/apps/promotions/context_processors.py
+++ b/oscar/apps/promotions/context_processors.py
@@ -49,8 +49,3 @@ def split_by_position(linked_promotions, context):
         if key not in context:
             context[key] = []
         context[key].append(promotion)
-
-
-
-
-
diff --git a/oscar/apps/shipping/base.py b/oscar/apps/shipping/base.py
index b901d68..93228cb 100644
--- a/oscar/apps/shipping/base.py
+++ b/oscar/apps/shipping/base.py
@@ -49,6 +49,8 @@ class Base(object):
         return self.charge_incl_tax - self.charge_excl_tax

     def _set_tax(self, value):
+        # Tax can be set later on in territories where it is now known up
+        # front.
         self.charge_incl_tax = self.charge_excl_tax + value
         self.is_tax_known = True

diff --git a/oscar/apps/shipping/methods.py b/oscar/apps/shipping/methods.py
index 63d7e32..2c350fa 100644
--- a/oscar/apps/shipping/methods.py
+++ b/oscar/apps/shipping/methods.py
@@ -91,6 +91,33 @@ class OfferDiscount(Base):
         return self.method.is_tax_known

     @property
+    def charge_excl_tax(self):
+        raise NotImplemented()
+
+    @property
+    def charge_incl_tax(self):
+        raise NotImplemented()
+
+
+class TaxExclusiveOfferDiscount(OfferDiscount):
+
+    @property
+    def charge_excl_tax(self):
+        parent_charge = self.method.charge_excl_tax
+        discount = self.offer.shipping_discount(parent_charge)
+        return parent_charge - discount
+
+    @property
+    def charge_incl_tax(self):
+        """
+        Tax needs to be assigned later on
+        """
+        return self.charge_excl_tax + self.tax
+
+
+class TaxInclusiveOfferDiscount(OfferDiscount):
+
+    @property
     def charge_incl_tax(self):
         parent_charge = self.method.charge_incl_tax
         discount = self.offer.shipping_discount(parent_charge)
@@ -105,5 +132,6 @@ class OfferDiscount(Base):
         charge_incl_tax = self.charge_incl_tax
         if parent_charge_incl_tax == 0:
             return D('0.00')
-        return parent_charge_excl_tax * (charge_incl_tax /
-                                         parent_charge_incl_tax)
+        charge = parent_charge_excl_tax * (charge_incl_tax /
+                                           parent_charge_incl_tax)
+        return charge.quantize(D('0.01'))
diff --git a/oscar/apps/shipping/repository.py b/oscar/apps/shipping/repository.py
index 2b51692..debfdd2 100644
--- a/oscar/apps/shipping/repository.py
+++ b/oscar/apps/shipping/repository.py
@@ -63,14 +63,23 @@ class Repository(object):
         Prime an individual method instance
         """
         method.set_basket(basket)
+
         # If the basket has a shipping offer, wrap the shipping method with a
         # decorating class that applies the offer discount to the shipping
         # charge.
         if basket.offer_applications.shipping_discounts:
             # We assume there is only one shipping discount available
             discount = basket.offer_applications.shipping_discounts[0]
-            if method.charge_incl_tax > D('0.00'):
-                return methods.OfferDiscount(method, discount['offer'])
+            if method.charge_excl_tax > D('0.00'):
+                if method.is_tax_known:
+                    return methods.TaxInclusiveOfferDiscount(
+                        method, discount['offer'])
+                else:
+                    # When returning a tax exclusive discount, it is assumed
+                    # that this will be used to calculate taxes which will then
+                    # be assigned directly to the method instance.
+                    return methods.TaxExclusiveOfferDiscount(
+                        method, discount['offer'])
         return method

     def find_by_code(self, code, basket):
diff --git a/oscar/test/factories.py b/oscar/test/factories.py
index 6aec8ba..67cff91 100644
--- a/oscar/test/factories.py
+++ b/oscar/test/factories.py
@@ -60,7 +60,7 @@ def create_stockinfo(record):
 def create_product(upc=None, title=u"Dummy title",
                    product_class=u"Dummy item class",
                    partner=u"Dummy partner", partner_sku=None, price=None,
-                   num_in_stock=None, attributes=None,
+                   num_in_stock=None, attributes=None,
                    partner_users=None, **kwargs):
     """
     Helper method for creating products that are used in tests.
@@ -88,7 +88,7 @@ def create_product(upc=None, title=u"Dummy title",
     return product

-def create_basket(empty=False, strategy=None):
+def create_basket(empty=False):
     basket = Basket.objects.create()
     basket.strategy = strategy.Default()
     if not empty:
diff --git a/tests/integration/offer/shipping_percentage_benefit_tests.py b/tests/integration/offer/shipping_percentage_benefit_tests.py
index 892a0cb..d72e45e 100644
--- a/tests/integration/offer/shipping_percentage_benefit_tests.py
+++ b/tests/integration/offer/shipping_percentage_benefit_tests.py
@@ -2,9 +2,20 @@ from decimal import Decimal as D

 from django.test import TestCase

-from oscar.apps.offer import models
+from oscar.apps.offer import models, utils
 from oscar.test.basket import add_product
 from oscar.test import factories
+from oscar.apps.shipping import repository, methods
+
+
+class ExcludingTax(methods.Base):
+    charge_excl_tax = D('10.00')
+
+
+class IncludingTax(methods.Base):
+    charge_excl_tax = D('10.00')
+    charge_incl_tax = D('12.00')
+    is_tax_known = True

 class TestAShippingPercentageDiscountAppliedWithCountCondition(TestCase):
@@ -44,3 +55,26 @@ class TestAShippingPercentageDiscountAppliedWithCountCondition(TestCase):
         self.assertEqual(2, self.basket.num_items_with_discount)
         self.assertEqual(1, self.basket.num_items_without_discount)
         self.assertTrue(result.affects_shipping)
+
+    def test_applies_correctly_to_shipping_method_without_tax(self):
+        add_product(self.basket, D('12.00'), 3)
+
+        # Apply offers to basket
+        utils.Applicator().apply_offers(self.basket, [self.offer])
+
+        repo = repository.Repository()
+        raw_method = ExcludingTax()
+        method = repo.prime_method(self.basket, raw_method)
+        self.assertEqual(D('5.00'), method.charge_excl_tax)
+
+    def test_applies_correctly_to_shipping_method_with_tax(self):
+        add_product(self.basket, D('12.00'), 3)
+
+        # Apply offers to basket
+        utils.Applicator().apply_offers(self.basket, [self.offer])
+
+        repo = repository.Repository()
+        raw_method = IncludingTax()
+        method = repo.prime_method(self.basket, raw_method)
+        self.assertEqual(D('6.00'), method.charge_incl_tax)
+        self.assertEqual(D('5.00'), method.charge_excl_tax)
Don't render blank table in categories dashboard
Improve catalogue admin for options
Add fixture for creating a variant product

And load it for the sandbox by default.
Extend strategy class to work with variant products

The strategy API has gained a new method: fetch_for_group, which is for
fetching stock information about a group/parent/abstract product.  Like
the existing fetch method, this is highly customisable.

For a group product, we fetch all relevant variants and stockrecords; we
then pass these to the methods that determine availability and pricing.

The pricing display logic for a group product has been simplified a bit.
We now just look at the first stockrecord and use that price - we no
longer say "From £X".  This seems to be a more common pattern in the
wild and allows the templates to be simplfied as they no longer need to
handle group and standalone products separately.
Ensure that only available variants are in basket dropdown
Use a custom widget to show unavailable variants in dropdown

We define a custom select widget which allows certain choices to be
marked as disabled.  We use this to include unavailable variants in the
add-to-basket dropdown.
Adjust sample strategies for UK and US

- Improve documentation on what they are for
- Move tax rate into UK strategy
Ensure get_discount uses the correct parent charge for discount

Before this, it was wrongly using the tax inclusive charge each time.
Fix all flake8 issues.
Update to flake8 2.1
Remove threshold and ignored warnings from lint.sh

Also output the errors directly now that it's not over 100 anymore.
Move flake8 configuration to tox.ini

That way it is picked up for example by vim.