Repository: saleor/saleor

Issue:
AttributeValue objects with empty slugs break product variations
### What I'm trying to achieve
I want to use attributes in variation products which I created via Python.

### Steps to reproduce the problem
1. Create `AttributeValue` objects without slug (empty string) via Python
```python
In [6]: for a in AttributeValue.objects.all(): 
   ...:     print(a.__dict__) 
   ...:                                                                                                                                                                                                                                                                                                                                                                   
{'_state': <django.db.models.base.ModelState object at 0x117ea6048>, 'id': 2, 'sort_order': 0, 'name': '60', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea60b8>, 'id': 1, 'sort_order': 0, 'name': '2', 'value': '', 'slug': '', 'attribute_id': 29}
{'_state': <django.db.models.base.ModelState object at 0x117ea6128>, 'id': 3, 'sort_order': 1, 'name': '', 'value': '', 'slug': '', 'attribute_id': 29}
{'_state': <django.db.models.base.ModelState object at 0x117ea6198>, 'id': 4, 'sort_order': 1, 'name': '70', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea6208>, 'id': 5, 'sort_order': 2, 'name': '105', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea6278>, 'id': 6, 'sort_order': 2, 'name': '3', 'value': '', 'slug': '', 'attribute_id': 29}
{'_state': <django.db.models.base.ModelState object at 0x117ea62e8>, 'id': 9, 'sort_order': 3, 'name': '4', 'value': '', 'slug': '', 'attribute_id': 29}
{'_state': <django.db.models.base.ModelState object at 0x117ea6358>, 'id': 7, 'sort_order': 3, 'name': '40', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea63c8>, 'id': 8, 'sort_order': 4, 'name': '50', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea6438>, 'id': 13, 'sort_order': 4, 'name': '1', 'value': '', 'slug': '', 'attribute_id': 29}
{'_state': <django.db.models.base.ModelState object at 0x117ea64a8>, 'id': 10, 'sort_order': 5, 'name': '100', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea6518>, 'id': 11, 'sort_order': 6, 'name': '45', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea6588>, 'id': 12, 'sort_order': 7, 'name': '25', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea65f8>, 'id': 14, 'sort_order': 8, 'name': '28', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea6668>, 'id': 15, 'sort_order': 9, 'name': '80', 'value': '', 'slug': '', 'attribute_id': 28}
{'_state': <django.db.models.base.ModelState object at 0x117ea66d8>, 'id': 16, 'sort_order': 10, 'name': '30', 'value': '', 'slug': '', 'attribute_id': 28}
```
2. Use them in variations for products

### What I expected to happen
Url of product page should have values for its variation attributes.


### How to fix:
Do not allow empty slugs in `AttributeValue`. BTW for data consistency it is also a good idea to have unique values for attributes which identify objects. The database backend should raise an exception. 


### Screenshots
Clicking the variation buttons, the url also results in:
http://localhost:8000/en/products/foo-25/?branches=&height=
DOM of variation picker button
```html
<label class="btn btn-secondary variant-picker__option active"><input name="4" type="radio" checked="">70</label>
````
(no value)

**System information**
Operating system: ArchLinux x86_64
Browser: FF nightly

### Workareound
```python
In [3]: for a in AttributeValue.objects.all(): 
   ...:     a.slug = a.name 
   ...:     a.save() 
```


Discussion:
`a.slug = a.name` should not be considered as valid workaround since name can have non ascii symbols, spaces and slugs by default of django SlugField should be unique among all `AttributeValues`.
Values have to be unique within an attribute and there is a `unique_together` check for that. The suggested way of creating those values would be through the API, which has necessary checks for that. But I agree it would be more consistent if we made the `slug` field required in the model.

Resolving patch:
diff --git a/saleor/product/models.py b/saleor/product/models.py
--- a/saleor/product/models.py
+++ b/saleor/product/models.py
@@ -854,6 +854,11 @@ def input_type(self):
     def get_ordering_queryset(self):
         return self.attribute.values.all()
 
+    def save(self, *args, **kwargs):
+        if not self.slug and self.name:
+            self.slug = slugify(self.name)
+        return super().save(*args, **kwargs)
+
 
 class AttributeValueTranslation(models.Model):
     language_code = models.CharField(max_length=10)


Test patch:
diff --git a/tests/test_product_attributes.py b/tests/test_product_attributes.py
--- a/tests/test_product_attributes.py
+++ b/tests/test_product_attributes.py
@@ -1,6 +1,7 @@
 from unittest.mock import MagicMock, Mock
 
 import pytest
+from django.utils.text import slugify
 from prices import Money
 
 from saleor.product import AttributeInputType
@@ -34,6 +35,11 @@ def variant_with_no_attributes(category):
     return variant
 
 
+def test_generate_slug_in_attribute_value_save(color_attribute):
+    value = AttributeValue.objects.create(attribute=color_attribute, name="Yellow")
+    assert value.slug == slugify("Yellow")
+
+
 def test_generate_name_for_variant(
     variant_with_no_attributes, color_attribute_without_values, size_attribute
 ):