Repository: django-oscar/django-oscar

Issue:
Broken CommunicationEventType code creation after migration to AutoSlugField
I saw [discussion](https://github.com/tangentlabs/django-oscar/commit/0a9e6d1e53449cf071241fd923f9fea7204ce204#commitcomment-5704798) about this bug, but doesn't find related issue.

After migration to AutoSlugField we have difference between letter case of codes of communication event types. Django's SlugField hasn't any conversions to lower letters, but AutoSlugField have. So if communication event type was created in Django admin with code like "DELIVERED", it will have a slug "delivered" (exactly, lowercase). But UpdateView in `dashboard/communication/views.py` try to get communication event type with uppercase code ([here](https://github.com/tangentlabs/django-oscar/blob/master/oscar/apps/dashboard/communications/views.py#L32)):

``` python
def get_object(self, **kwargs):
        return get_object_or_404(self.model,
                                 code=self.kwargs['code'].upper())
```

And after fail on it throw 404.

Broken CommunicationEventType code creation after migration to AutoSlugField
I saw [discussion](https://github.com/tangentlabs/django-oscar/commit/0a9e6d1e53449cf071241fd923f9fea7204ce204#commitcomment-5704798) about this bug, but doesn't find related issue.

After migration to AutoSlugField we have difference between letter case of codes of communication event types. Django's SlugField hasn't any conversions to lower letters, but AutoSlugField have. So if communication event type was created in Django admin with code like "DELIVERED", it will have a slug "delivered" (exactly, lowercase). But UpdateView in `dashboard/communication/views.py` try to get communication event type with uppercase code ([here](https://github.com/tangentlabs/django-oscar/blob/master/oscar/apps/dashboard/communications/views.py#L32)):

``` python
def get_object(self, **kwargs):
        return get_object_or_404(self.model,
                                 code=self.kwargs['code'].upper())
```

And after fail on it throw 404.



Discussion:

Resolving patch:
diff --git a/oscar/apps/customer/abstract_models.py b/oscar/apps/customer/abstract_models.py
--- a/oscar/apps/customer/abstract_models.py
+++ b/oscar/apps/customer/abstract_models.py
@@ -1,3 +1,4 @@
+import six
 import hashlib
 import random
 
@@ -133,7 +134,8 @@ class AbstractCommunicationEventType(models.Model):
     # Code used for looking up this event programmatically.
     # eg. PASSWORD_RESET
     code = AutoSlugField(_('Code'), max_length=128, unique=True,
-                         populate_from='name')
+                         populate_from='name', separator=six.u("_"),
+                         uppercase=True)
 
     #: Name is the friendly description of an event for use in the admin
     name = models.CharField(
diff --git a/oscar/models/fields/autoslugfield.py b/oscar/models/fields/autoslugfield.py
--- a/oscar/models/fields/autoslugfield.py
+++ b/oscar/models/fields/autoslugfield.py
@@ -69,6 +69,7 @@ def __init__(self, *args, **kwargs):
             self._populate_from = populate_from
         self.separator = kwargs.pop('separator', six.u('-'))
         self.overwrite = kwargs.pop('overwrite', False)
+        self.uppercase = kwargs.pop('uppercase', False)
         self.allow_duplicates = kwargs.pop('allow_duplicates', False)
         super(AutoSlugField, self).__init__(*args, **kwargs)
 
@@ -120,6 +121,10 @@ def create_slug(self, model_instance, add):  # NOQA (too complex)
         if slug_len:
             slug = slug[:slug_len]
         slug = self._slug_strip(slug)
+
+        if self.uppercase:
+            slug = slug.upper()
+
         original_slug = slug
 
         if self.allow_duplicates:


Test patch:
diff --git a/tests/_site/model_tests_app/models.py b/tests/_site/model_tests_app/models.py
--- a/tests/_site/model_tests_app/models.py
+++ b/tests/_site/model_tests_app/models.py
@@ -1,3 +1,4 @@
+import six
 from django.db import models
 from oscar.models.fields import AutoSlugField
 
@@ -9,3 +10,10 @@ class SluggedTestModel(models.Model):
 
 class ChildSluggedTestModel(SluggedTestModel):
     pass
+
+
+class CustomSluggedTestModel(models.Model):
+    title = models.CharField(max_length=42)
+    slug = AutoSlugField(populate_from='title',
+                         separator=six.u("_"),
+                         uppercase=True)
diff --git a/tests/functional/dashboard/communication_tests.py b/tests/functional/dashboard/communication_tests.py
--- a/tests/functional/dashboard/communication_tests.py
+++ b/tests/functional/dashboard/communication_tests.py
@@ -13,7 +13,7 @@ class TestAnAdmin(WebTestCase):
     def setUp(self):
         self.staff = G(User, is_staff=True, username='1234')
         self.commtype = CommunicationEventType.objects.create(
-            code='PASSWORD_RESET', name="Password reset",
+            name="Password reset",
             category=CommunicationEventType.USER_RELATED)
 
     def test_can_preview_an_email(self):
diff --git a/tests/unit/core/autoslugfield_tests.py b/tests/unit/core/autoslugfield_tests.py
--- a/tests/unit/core/autoslugfield_tests.py
+++ b/tests/unit/core/autoslugfield_tests.py
@@ -33,6 +33,7 @@
 
 SluggedTestModel = get_model('model_tests_app', 'sluggedtestmodel')
 ChildSluggedTestModel = get_model('model_tests_app', 'childsluggedtestmodel')
+CustomSluggedTestModel = get_model('model_tests_app', 'CustomSluggedTestModel')
 
 
 class AutoSlugFieldTest(TestCase):
@@ -124,6 +125,15 @@ def testInheritanceCreatesNextSlug(self):
         o.save()
         self.assertEqual(o.slug, 'foo-3')
 
+    def test_separator_and_uppercase_options(self):
+        m = CustomSluggedTestModel(title="Password reset")
+        m.save()
+        self.assertEqual(m.slug, 'PASSWORD_RESET')
+
+        m = CustomSluggedTestModel(title="Password reset")
+        m.save()
+        self.assertEqual(m.slug, 'PASSWORD_RESET_2')
+
     @unittest.skipIf(django.VERSION[0] <= 1 and django.VERSION[1] <= 6,
                      "Migrations are handled by south in Django <1.7")
     def test_17_migration(self):