Repository: django-oscar/django-oscar

Issue:
Error on "incentive" step of the offer wizard
When creating offer through the offer wizard and on the second step, "Incentive", select "range" and "type" but do not fill in "value" it gives next exception:

```python
Traceback (most recent call last):
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/core/handlers/base.py", line 149, in get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/core/handlers/base.py", line 147, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/contextlib.py", line 30, in inner
    return func(*args, **kwds)
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/contrib/auth/decorators.py", line 23, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/a1/projects/djangooscar/src/oscar/apps/dashboard/offers/views.py", line 93, in dispatch
    **kwargs)
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/views/generic/base.py", line 88, in dispatch
    return handler(request, *args, **kwargs)
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/views/generic/edit.py", line 221, in post
    if form.is_valid():
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/forms/forms.py", line 161, in is_valid
    return self.is_bound and not self.errors
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/forms/forms.py", line 153, in errors
    self.full_clean()
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/forms/forms.py", line 364, in full_clean
    self._post_clean()
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/forms/models.py", line 396, in _post_clean
    self.instance.full_clean(exclude=exclude, validate_unique=False)
  File "/Users/a1/projects/djangooscar/env3/lib/python3.5/site-packages/django/db/models/base.py", line 1129, in full_clean
    self.clean()
  File "/Users/a1/projects/djangooscar/src/oscar/apps/offer/abstract_models.py", line 497, in clean
    getattr(self, method_name)()
  File "/Users/a1/projects/djangooscar/src/oscar/apps/offer/abstract_models.py", line 515, in clean_percentage
    if self.value > 100:
TypeError: unorderable types: NoneType() > int()
```


Discussion:

Resolving patch:
diff --git a/src/oscar/apps/offer/abstract_models.py b/src/oscar/apps/offer/abstract_models.py
--- a/src/oscar/apps/offer/abstract_models.py
+++ b/src/oscar/apps/offer/abstract_models.py
@@ -512,6 +512,9 @@ def clean_percentage(self):
         if not self.range:
             raise exceptions.ValidationError(
                 _("Percentage benefits require a product range"))
+        if not self.value:
+            raise exceptions.ValidationError(
+                _("Percentage discount benefits require a value"))
         if self.value > 100:
             raise exceptions.ValidationError(
                 _("Percentage discount cannot be greater than 100"))


Test patch:
diff --git a/tests/integration/offer/percentage_benefit_tests.py b/tests/integration/offer/percentage_benefit_tests.py
--- a/tests/integration/offer/percentage_benefit_tests.py
+++ b/tests/integration/offer/percentage_benefit_tests.py
@@ -1,5 +1,6 @@
 from decimal import Decimal as D
 
+from django.core import exceptions
 from django.test import TestCase
 import mock
 
@@ -179,3 +180,15 @@ def test_applies_correctly_to_basket_which_exceeds_condition(self):
         self.assertEqual(1 * D('4.00') * D('0.2'), result.discount)
         self.assertEqual(3, self.basket.num_items_with_discount)
         self.assertEqual(0, self.basket.num_items_without_discount)
+
+
+class TestAPercentageDiscountBenefit(TestCase):
+
+    def test_requires_a_benefit_value(self):
+        rng = models.Range.objects.create(
+            name="", includes_all_products=True)
+        benefit = models.Benefit.objects.create(
+            type=models.Benefit.PERCENTAGE, range=rng
+        )
+        with self.assertRaises(exceptions.ValidationError):
+            benefit.clean()