Repository: saleor/saleor

Issue:
Unable to create a sale on categories without specifiying products


### What I'm trying to achieve
Create a sale on a category.

### Steps to reproduce the problem

1. Go to dashboard > sales > create or edit
2. Remove any selected products
3. Only specify categories

### What I expected to happen
Have the sale applied without selecting any product.

### What happened instead/how it failed
Error as below.

![](https://i.imgur.com/I0VJ32s.png)


Discussion:

Resolving patch:
diff --git a/saleor/dashboard/discount/forms.py b/saleor/dashboard/discount/forms.py
--- a/saleor/dashboard/discount/forms.py
+++ b/saleor/dashboard/discount/forms.py
@@ -17,7 +17,7 @@
 class SaleForm(forms.ModelForm):
     products = AjaxSelect2MultipleChoiceField(
         queryset=Product.objects.all(),
-        fetch_data_url=reverse_lazy('dashboard:ajax-products'), required=True)
+        fetch_data_url=reverse_lazy('dashboard:ajax-products'), required=False)
 
     class Meta:
         model = Sale
@@ -52,6 +52,13 @@ def clean(self):
             self.add_error('value', pgettext_lazy(
                 'Sale (discount) error',
                 'Sale cannot exceed 100%'))
+        products = cleaned_data.get('products')
+        categories = cleaned_data.get('categories')
+        if not products and not categories:
+            raise forms.ValidationError(pgettext_lazy(
+                'Sale (discount) error',
+                'A single sale must point to at least one product and/or '
+                'category.'))
         return cleaned_data
 
 


Test patch:
diff --git a/tests/dashboard/test_discount.py b/tests/dashboard/test_discount.py
--- a/tests/dashboard/test_discount.py
+++ b/tests/dashboard/test_discount.py
@@ -42,3 +42,50 @@ def test_voucher_shipping_add(admin_client):
     assert voucher.discount_value_type == DiscountValueType.FIXED
     assert voucher.discount_value == Decimal('15.99')
     assert voucher.limit == Money('59.99', 'USD')
+
+
+def test_view_sale_add(admin_client, default_category):
+    url = reverse('dashboard:sale-add')
+    data = {
+        'name': 'Free products',
+        'type': DiscountValueType.PERCENTAGE,
+        'value': 100,
+        'categories': [default_category.id]}
+
+    response = admin_client.post(url, data)
+
+    assert response.status_code == 302
+    assert Sale.objects.count() == 1
+    sale = Sale.objects.first()
+    assert sale.name == data['name']
+    assert default_category in sale.categories.all()
+
+
+def test_view_sale_add_requires_product_or_category(
+        admin_client, default_category, product_in_stock):
+    url = reverse('dashboard:sale-add')
+    data = {
+        'name': 'Free products',
+        'type': DiscountValueType.PERCENTAGE,
+        'value': 100}
+
+    response = admin_client.post(url, data)
+
+    assert response.status_code == 200
+    assert Sale.objects.count() == 0
+
+    data_with_category = data.copy()
+    data_with_category.update({'categories': [default_category.id]})
+
+    response = admin_client.post(url, data_with_category)
+
+    assert response.status_code == 302
+    assert Sale.objects.count() == 1
+
+    data_with_product = data.copy()
+    data_with_product.update({'products': [product_in_stock.id]})
+
+    response = admin_client.post(url, data_with_product)
+
+    assert response.status_code == 302
+    assert Sale.objects.count() == 2