16 Commits
2.4 ... v2.9.2

Author SHA1 Message Date
Swen Kooij
b9a4d3be2c Bump version number to 2.9.2 2017-02-16 10:20:09 +02:00
Swen Kooij
6d7a937eac Don't regenerate slug if not needed 2017-02-16 10:13:10 +02:00
Swen Kooij
2e9b83e49b Bump version to 2.9.1 2017-02-16 09:36:34 +02:00
Swen Kooij
679dcafef6 Pop kwargs after calling super constructor 2017-02-16 09:36:26 +02:00
Swen Kooij
ad2ef34546 Bump version number to 2.9 2017-02-15 19:29:29 +02:00
Swen Kooij
1317023160 Bump version number to 2.8 2017-02-15 19:13:40 +02:00
Swen Kooij
ca6b1c88fa Add option include_time to LocalizedUniqueSlugField 2017-02-15 19:13:19 +02:00
Swen Kooij
64c3c06612 Bumped version number to 2.7 2017-02-09 14:57:25 +02:00
Swen Kooij
b121dfc2d7 Added __eq__ operator to LocalizedValue 2017-02-09 14:57:08 +02:00
Swen Kooij
d529da8886 Fixed bug with with missing populate_from 2017-02-03 11:14:37 +02:00
Swen Kooij
ca879087ea Bumped version to 2.6 2017-02-03 10:41:05 +02:00
Swen Kooij
302a64a02c Updated base classes in documentation 2017-02-03 10:40:37 +02:00
Swen Kooij
bb11253207 Moved retry mechanism to mixin 2017-02-03 10:35:39 +02:00
Swen Kooij
5db87763fb Rename LocalizedMagicSlugField to LocalizedUniqueSlugField 2017-02-03 10:27:30 +02:00
Swen Kooij
759d03133b Bump version number to 2.5 2017-02-02 17:09:15 +02:00
Swen Kooij
d5ed3ced40 Raise IntegrityError if it's not about the slug 2017-02-02 17:08:06 +02:00
14 changed files with 290 additions and 143 deletions

View File

@@ -146,14 +146,14 @@ At the moment, it is not possible to select two languages to be marked as requir
.. code-block:: python .. code-block:: python
class MyModel(models.Model): class MyModel(LocalizedModel):
title = LocalizedField(required=True) title = LocalizedField(required=True)
* Make all languages optional: * Make all languages optional:
.. code-block:: python .. code-block:: python
class MyModel(models.Model): class MyModel(LocalizedModel):
title = LocalizedField(null=True) title = LocalizedField(null=True)
**Uniqueness** **Uniqueness**
@@ -164,7 +164,7 @@ By default the values stored in a ``LocalizedField`` are *not unique*. You can e
.. code-block:: python .. code-block:: python
class MyModel(models.Model): class MyModel(LocalizedModel):
title = LocalizedField(uniqueness=['en', 'ro']) title = LocalizedField(uniqueness=['en', 'ro'])
* Enforce uniqueness for **all** languages: * Enforce uniqueness for **all** languages:
@@ -173,14 +173,14 @@ By default the values stored in a ``LocalizedField`` are *not unique*. You can e
from localized_fields import get_language_codes from localized_fields import get_language_codes
class MyModel(models.Model): class MyModel(LocalizedModel):
title = LocalizedField(uniqueness=get_language_codes()) title = LocalizedField(uniqueness=get_language_codes())
* Enforce uniqueness for one ore more languages **together** (similar to Django's ``unique_together``): * Enforce uniqueness for one ore more languages **together** (similar to Django's ``unique_together``):
.. code-block:: python .. code-block:: python
class MyModel(models.Model): class MyModel(LocalizedModel):
title = LocalizedField(uniqueness=[('en', 'ro')]) title = LocalizedField(uniqueness=[('en', 'ro')])
* Enforce uniqueness for **all** languages **together**: * Enforce uniqueness for **all** languages **together**:
@@ -189,7 +189,7 @@ By default the values stored in a ``LocalizedField`` are *not unique*. You can e
from localized_fields import get_language_codes from localized_fields import get_language_codes
class MyModel(models.Model): class MyModel(LocalizedModel):
title = LocalizedField(uniqueness=[(*get_language_codes())]) title = LocalizedField(uniqueness=[(*get_language_codes())])
@@ -197,19 +197,29 @@ Other fields
^^^^^^^^^^^^ ^^^^^^^^^^^^
Besides ``LocalizedField``, there's also: Besides ``LocalizedField``, there's also:
* ``LocalizedMagicSlugField`` * ``LocalizedUniqueSlugField``
Successor of ``LocalizedAutoSlugField`` that fixes concurrency issues and enforces Successor of ``LocalizedAutoSlugField`` that fixes concurrency issues and enforces
uniqueness of slugs on a database level. Usage is the exact same: uniqueness of slugs on a database level. Usage is the exact same:
.. code-block:: python .. code-block:: python
from localized_fields.models import LocalizedModel from localized_fields import (LocalizedModel,
from localized_fields.fields import (LocalizedField, AtomicSlugRetryMixin,
LocalizedMagicSlugField) LocalizedField,
LocalizedUniqueSlugField)
class MyModel(LocalizedModel): class MyModel(AtomicSlugRetryMixin, LocalizedModel):
title = LocalizedField() title = LocalizedField()
slug = LocalizedMagicSlugField(populate_from='title') slug = LocalizedUniqueSlugField(populate_from='title')
By setting the option ``include_time=True``
.. code-block:: python
slug = LocalizedUniqueSlugField(populate_from='title', include_time=True)
You can instruct the field to include a part of the current time into
the resulting slug. This is useful if you're running into a lot of collisions.
* ``LocalizedAutoSlugField`` * ``LocalizedAutoSlugField``
Automatically creates a slug for every language from the specified field. Automatically creates a slug for every language from the specified field.
@@ -218,15 +228,15 @@ Besides ``LocalizedField``, there's also:
.. code-block:: python .. code-block:: python
from localized_fields.models import LocalizedModel from localized_fields import (LocalizedModel,
from localized_fields.fields import (LocalizedField, LocalizedField,
LocalizedAutoSlugField) LocalizedUniqueSlugField)
class MyModel(LocalizedModel): class MyModel(LocalizedModel):
title = LocalizedField() title = LocalizedField()
slug = LocalizedAutoSlugField(populate_from='title') slug = LocalizedAutoSlugField(populate_from='title')
This implementation is **NOT** concurrency safe, prefer ``LocalizedMagicSlugField``. This implementation is **NOT** concurrency safe, prefer ``LocalizedUniqueSlugField``.
* ``LocalizedBleachField`` * ``LocalizedBleachField``
Automatically bleaches the content of the field. Automatically bleaches the content of the field.
@@ -236,9 +246,9 @@ Besides ``LocalizedField``, there's also:
.. code-block:: python .. code-block:: python
from localized_fields.models import LocalizedModel from localized_fields import (LocalizedModel,
from localized_fields.fields import (LocalizedField, LocalizedField,
LocalizedBleachField) LocalizedBleachField)
class MyModel(LocalizedModel): class MyModel(LocalizedModel):
title = LocalizedField() title = LocalizedField()

View File

@@ -1,18 +1,20 @@
from .util import get_language_codes from .util import get_language_codes
from .forms import LocalizedFieldForm, LocalizedFieldWidget from .forms import LocalizedFieldForm, LocalizedFieldWidget
from .fields import (LocalizedField, LocalizedBleachField, from .fields import (LocalizedField, LocalizedBleachField,
LocalizedAutoSlugField, LocalizedMagicSlugField) LocalizedAutoSlugField, LocalizedUniqueSlugField)
from .localized_value import LocalizedValue from .mixins import AtomicSlugRetryMixin
from .models import LocalizedModel from .models import LocalizedModel
from .localized_value import LocalizedValue
__all__ = [ __all__ = [
'get_language_codes', 'get_language_codes',
'LocalizedField', 'LocalizedField',
'LocalizedValue', 'LocalizedValue',
'LocalizedAutoSlugField', 'LocalizedAutoSlugField',
'LocalizedMagicSlugField', 'LocalizedUniqueSlugField',
'LocalizedBleachField', 'LocalizedBleachField',
'LocalizedFieldWidget', 'LocalizedFieldWidget',
'LocalizedFieldForm', 'LocalizedFieldForm',
'LocalizedModel' 'LocalizedModel',
'AtomicSlugRetryMixin'
] ]

View File

@@ -34,14 +34,14 @@ def _get_backend_base():
'\'%s\' is not a valid database back-end.' '\'%s\' is not a valid database back-end.'
' The module does not define a DatabaseWrapper class.' ' The module does not define a DatabaseWrapper class.'
' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.'
)) ) % base_class_name)
if isinstance(base_class, Psycopg2DatabaseWrapper): if isinstance(base_class, Psycopg2DatabaseWrapper):
raise ImproperlyConfigured(( raise ImproperlyConfigured((
'\'%s\' is not a valid database back-end.' '\'%s\' is not a valid database back-end.'
' It does inherit from the PostgreSQL back-end.' ' It does inherit from the PostgreSQL back-end.'
' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.'
)) ) % base_class_name)
return base_class return base_class

View File

@@ -1,12 +1,12 @@
from .localized_field import LocalizedField from .localized_field import LocalizedField
from .localized_autoslug_field import LocalizedAutoSlugField from .localized_autoslug_field import LocalizedAutoSlugField
from .localized_magicslug_field import LocalizedMagicSlugField from .localized_uniqueslug_field import LocalizedUniqueSlugField
from .localized_bleach_field import LocalizedBleachField from .localized_bleach_field import LocalizedBleachField
__all__ = [ __all__ = [
'LocalizedField', 'LocalizedField',
'LocalizedAutoSlugField', 'LocalizedAutoSlugField',
'LocalizedMagicSlugField', 'LocalizedUniqueSlugField',
'LocalizedBleachField', 'LocalizedBleachField',
] ]

View File

@@ -1,4 +1,5 @@
from typing import Callable from typing import Callable
from datetime import datetime
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@@ -16,6 +17,7 @@ class LocalizedAutoSlugField(LocalizedField):
"""Initializes a new instance of :see:LocalizedAutoSlugField.""" """Initializes a new instance of :see:LocalizedAutoSlugField."""
self.populate_from = kwargs.pop('populate_from', None) self.populate_from = kwargs.pop('populate_from', None)
self.include_time = kwargs.pop('include_time', False)
super(LocalizedAutoSlugField, self).__init__( super(LocalizedAutoSlugField, self).__init__(
*args, *args,
@@ -30,6 +32,7 @@ class LocalizedAutoSlugField(LocalizedField):
LocalizedAutoSlugField, self).deconstruct() LocalizedAutoSlugField, self).deconstruct()
kwargs['populate_from'] = self.populate_from kwargs['populate_from'] = self.populate_from
kwargs['include_time'] = self.include_time
return name, path, args, kwargs return name, path, args, kwargs
def formfield(self, **kwargs): def formfield(self, **kwargs):
@@ -76,6 +79,9 @@ class LocalizedAutoSlugField(LocalizedField):
if not value: if not value:
continue continue
if self.include_time:
value += '-%s' % datetime.now().microsecond
def is_unique(slug: str, language: str) -> bool: def is_unique(slug: str, language: str) -> bool:
"""Gets whether the specified slug is unique.""" """Gets whether the specified slug is unique."""

View File

@@ -1,68 +0,0 @@
from django.conf import settings
from django.utils.text import slugify
from ..localized_value import LocalizedValue
from .localized_autoslug_field import LocalizedAutoSlugField
from ..util import get_language_codes
class LocalizedMagicSlugField(LocalizedAutoSlugField):
"""Automatically provides slugs for a localized
field upon saving."
An improved version of :see:LocalizedAutoSlugField,
which adds:
- Concurrency safety
- Improved performance
When in doubt, use this over :see:LocalizedAutoSlugField.
"""
def __init__(self, *args, **kwargs):
"""Initializes a new instance of :see:LocalizedMagicSlugField."""
self.populate_from = kwargs.pop('populate_from')
kwargs['uniqueness'] = kwargs.pop('uniqueness', get_language_codes())
super(LocalizedAutoSlugField, self).__init__(
*args,
**kwargs
)
def pre_save(self, instance, add: bool):
"""Ran just before the model is saved, allows us to built
the slug.
Arguments:
instance:
The model that is being saved.
add:
Indicates whether this is a new entry
to the database or an update.
Returns:
The localized slug that was generated.
"""
slugs = LocalizedValue()
for lang_code, _ in settings.LANGUAGES:
value = self._get_populate_from_value(
instance,
self.populate_from,
lang_code
)
if not value:
continue
slug = slugify(value, allow_unicode=True)
if instance.retries > 0:
slug += '-%d' % instance.retries
slugs.set(lang_code, slug)
setattr(instance, self.name, slugs)
return slugs

View File

@@ -0,0 +1,110 @@
from datetime import datetime
from django.conf import settings
from django.utils.text import slugify
from django.core.exceptions import ImproperlyConfigured
from ..util import get_language_codes
from ..mixins import AtomicSlugRetryMixin
from ..localized_value import LocalizedValue
from .localized_autoslug_field import LocalizedAutoSlugField
class LocalizedUniqueSlugField(LocalizedAutoSlugField):
"""Automatically provides slugs for a localized
field upon saving."
An improved version of :see:LocalizedAutoSlugField,
which adds:
- Concurrency safety
- Improved performance
When in doubt, use this over :see:LocalizedAutoSlugField.
Inherit from :see:AtomicSlugRetryMixin in your model to
make this field work properly.
"""
def __init__(self, *args, **kwargs):
"""Initializes a new instance of :see:LocalizedUniqueSlugField."""
kwargs['uniqueness'] = kwargs.pop('uniqueness', get_language_codes())
super(LocalizedUniqueSlugField, self).__init__(
*args,
**kwargs
)
self.populate_from = kwargs.pop('populate_from')
self.include_time = kwargs.pop('include_time', False)
def deconstruct(self):
"""Deconstructs the field into something the database
can store."""
name, path, args, kwargs = super(
LocalizedUniqueSlugField, self).deconstruct()
kwargs['populate_from'] = self.populate_from
kwargs['include_time'] = self.include_time
return name, path, args, kwargs
def pre_save(self, instance, add: bool):
"""Ran just before the model is saved, allows us to built
the slug.
Arguments:
instance:
The model that is being saved.
add:
Indicates whether this is a new entry
to the database or an update.
Returns:
The localized slug that was generated.
"""
if not isinstance(instance, AtomicSlugRetryMixin):
raise ImproperlyConfigured((
'Model \'%s\' does not inherit from AtomicSlugRetryMixin. '
'Without this, the LocalizedUniqueSlugField will not work.'
) % type(instance).__name__)
slugs = LocalizedValue()
for lang_code, _ in settings.LANGUAGES:
value = self._get_populate_from_value(
instance,
self.populate_from,
lang_code
)
if not value:
continue
slug = slugify(value, allow_unicode=True)
# verify whether it's needed to re-generate a slug,
# if not, re-use the same slug
if instance.pk is not None:
current_slug = getattr(instance, self.name).get(lang_code)
if current_slug is not None:
stripped_slug = current_slug[0:current_slug.rfind('-')]
if slug == stripped_slug:
slugs.set(lang_code, current_slug)
continue
if self.include_time:
slug += '-%d' % datetime.now().microsecond
if instance.retries > 0:
# do not add another - if we already added time
if not self.include_time:
slug += '-'
slug += '%d' % instance.retries
slugs.set(lang_code, slug)
setattr(instance, self.name, slugs)
return slugs

View File

@@ -76,6 +76,21 @@ class LocalizedValue:
return value or '' return value or ''
def __eq__(self, other):
"""Compares :paramref:self to :paramref:other for
equality.
Returns:
True when :paramref:self is equal to :paramref:other.
And False when they are not.
"""
for lang_code, _ in settings.LANGUAGES:
if self.get(lang_code) != other.get(lang_code):
return False
return True
def __repr__(self): # pragma: no cover def __repr__(self): # pragma: no cover
"""Gets a textual representation of this object.""" """Gets a textual representation of this object."""

View File

@@ -0,0 +1,38 @@
from django.db import transaction
from django.conf import settings
from django.db.utils import IntegrityError
class AtomicSlugRetryMixin:
"""Makes :see:LocalizedUniqueSlugField work by retrying upon
violation of the UNIQUE constraint."""
def save(self, *args, **kwargs):
"""Saves this model instance to the database."""
max_retries = getattr(
settings,
'LOCALIZED_FIELDS_MAX_RETRIES',
100
)
if not hasattr(self, 'retries'):
self.retries = 0
with transaction.atomic():
try:
return super().save(*args, **kwargs)
except IntegrityError as ex:
# this is as retarded as it looks, there's no
# way we can put the retry logic inside the slug
# field class... we can also not only catch exceptions
# that apply to slug fields... so yea.. this is as
# retarded as it gets... i am sorry :(
if 'slug' not in str(ex):
raise ex
if self.retries >= max_retries:
raise ex
self.retries += 1
return self.save()

View File

@@ -34,31 +34,3 @@ class LocalizedModel(models.Model):
value = LocalizedValue() value = LocalizedValue()
setattr(self, field.name, value) setattr(self, field.name, value)
def save(self, *args, **kwargs):
"""Saves this model instance to the database."""
max_retries = getattr(
settings,
'LOCALIZED_FIELDS_MAX_RETRIES',
100
)
if not hasattr(self, 'retries'):
self.retries = 0
with transaction.atomic():
try:
return super(LocalizedModel, self).save(*args, **kwargs)
except IntegrityError as ex:
# this is as retarded as it looks, there's no
# way we can put the retry logic inside the slug
# field class... we can also not only catch exceptions
# that apply to slug fields... so yea.. this is as
# retarded as it gets... i am sorry :(
if 'slug' in str(ex):
if self.retries >= max_retries:
raise ex
self.retries += 1
return self.save()

View File

@@ -7,7 +7,7 @@ with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
setup( setup(
name='django-localized-fields', name='django-localized-fields',
version='2.4', version='2.9.2',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
license='MIT License', license='MIT License',

View File

@@ -2,7 +2,7 @@ from django.db import connection, migrations
from django.db.migrations.executor import MigrationExecutor from django.db.migrations.executor import MigrationExecutor
from django.contrib.postgres.operations import HStoreExtension from django.contrib.postgres.operations import HStoreExtension
from localized_fields import LocalizedModel from localized_fields import LocalizedModel, AtomicSlugRetryMixin
def define_fake_model(name='TestModel', fields=None): def define_fake_model(name='TestModel', fields=None):
@@ -14,7 +14,7 @@ def define_fake_model(name='TestModel', fields=None):
if fields: if fields:
attributes.update(fields) attributes.update(fields)
model = type(name, (LocalizedModel,), attributes) model = type(name, (AtomicSlugRetryMixin,LocalizedModel,), attributes)
return model return model

View File

@@ -100,6 +100,19 @@ class LocalizedValueTestCase(TestCase):
translation.activate(language) translation.activate(language)
assert str(localized_value) == value assert str(localized_value) == value
@staticmethod
def test_eq():
"""Tests whether the __eq__ operator
of :see:LocalizedValue works properly."""
a = LocalizedValue({'en': 'a', 'ar': 'b'})
b = LocalizedValue({'en': 'a', 'ar': 'b'})
assert a == b
b.en = 'b'
assert a != b
@staticmethod @staticmethod
def test_str_fallback(): def test_str_fallback():
"""Tests whether the :see:LocalizedValue """Tests whether the :see:LocalizedValue

View File

@@ -1,11 +1,12 @@
import copy
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.utils.text import slugify
from localized_fields import (LocalizedField, LocalizedAutoSlugField, from localized_fields import (LocalizedField, LocalizedAutoSlugField,
LocalizedMagicSlugField) LocalizedUniqueSlugField)
from django.utils.text import slugify
from .fake_model import get_fake_model from .fake_model import get_fake_model
@@ -31,10 +32,10 @@ class LocalizedSlugFieldTestCase(TestCase):
) )
cls.MagicSlugModel = get_fake_model( cls.MagicSlugModel = get_fake_model(
'LocalizedMagicSlugFieldTestModel', 'LocalizedUniqueSlugFieldTestModel',
{ {
'title': LocalizedField(), 'title': LocalizedField(),
'slug': LocalizedMagicSlugField(populate_from='title') 'slug': LocalizedUniqueSlugField(populate_from='title')
} }
) )
@@ -43,7 +44,7 @@ class LocalizedSlugFieldTestCase(TestCase):
cls._test_populate(cls.AutoSlugModel) cls._test_populate(cls.AutoSlugModel)
@classmethod @classmethod
def test_populate_magic(cls): def test_populate_unique(cls):
cls._test_populate(cls.MagicSlugModel) cls._test_populate(cls.MagicSlugModel)
@classmethod @classmethod
@@ -51,7 +52,7 @@ class LocalizedSlugFieldTestCase(TestCase):
cls._test_populate_multiple_languages(cls.AutoSlugModel) cls._test_populate_multiple_languages(cls.AutoSlugModel)
@classmethod @classmethod
def test_populate_multiple_languages_magic(cls): def test_populate_multiple_languages_unique(cls):
cls._test_populate_multiple_languages(cls.MagicSlugModel) cls._test_populate_multiple_languages(cls.MagicSlugModel)
@classmethod @classmethod
@@ -59,14 +60,62 @@ class LocalizedSlugFieldTestCase(TestCase):
cls._test_unique_slug(cls.AutoSlugModel) cls._test_unique_slug(cls.AutoSlugModel)
@classmethod @classmethod
def test_unique_slug_magic(cls): def test_unique_slug_unique(cls):
cls._test_unique_slug(cls.MagicSlugModel) cls._test_unique_slug(cls.MagicSlugModel)
def test_unique_slug_magic_max_retries(self): @staticmethod
"""Tests whether the magic slug implementation doesn't def test_unique_slug_with_time():
"""Tests whether the primary key is included in
the slug when the 'use_pk' option is enabled."""
title = 'myuniquetitle'
PkModel = get_fake_model(
'PkModel',
{
'title': LocalizedField(),
'slug': LocalizedUniqueSlugField(populate_from='title', include_time=True)
}
)
obj = PkModel()
obj.title.en = title
obj.save()
assert obj.slug.en.startswith('%s-' % title)
@classmethod
def test_uniue_slug_no_change(cls):
"""Tests whether slugs are not re-generated if not needed."""
NoChangeSlugModel = get_fake_model(
'NoChangeSlugModel',
{
'title': LocalizedField(),
'slug': LocalizedUniqueSlugField(populate_from='title', include_time=True)
}
)
title = 'myuniquetitle'
obj = NoChangeSlugModel()
obj.title.en = title
obj.title.nl = title
obj.save()
old_slug_en = copy.deepcopy(obj.slug.en)
old_slug_nl = copy.deepcopy(obj.slug.nl)
obj.title.nl += 'beer'
obj.save()
assert old_slug_en == obj.slug.en
assert old_slug_nl != obj.slug.nl
def test_unique_slug_unique_max_retries(self):
"""Tests whether the unique slug implementation doesn't
try to find a slug forever and gives up after a while.""" try to find a slug forever and gives up after a while."""
title = 'mymagictitle' title = 'myuniquetitle'
obj = self.MagicSlugModel() obj = self.MagicSlugModel()
obj.title.en = title obj.title.en = title
@@ -83,7 +132,7 @@ class LocalizedSlugFieldTestCase(TestCase):
cls._test_unique_slug_utf(cls.AutoSlugModel) cls._test_unique_slug_utf(cls.AutoSlugModel)
@classmethod @classmethod
def test_unique_slug_utf_magic(cls): def test_unique_slug_utf_unique(cls):
cls._test_unique_slug_utf(cls.MagicSlugModel) cls._test_unique_slug_utf(cls.MagicSlugModel)
@classmethod @classmethod
@@ -91,16 +140,16 @@ class LocalizedSlugFieldTestCase(TestCase):
cls._test_deconstruct(LocalizedAutoSlugField) cls._test_deconstruct(LocalizedAutoSlugField)
@classmethod @classmethod
def test_deconstruct_magic(cls): def test_deconstruct_unique(cls):
cls._test_deconstruct(LocalizedMagicSlugField) cls._test_deconstruct(LocalizedUniqueSlugField)
@classmethod @classmethod
def test_formfield_auto(cls): def test_formfield_auto(cls):
cls._test_formfield(LocalizedAutoSlugField) cls._test_formfield(LocalizedAutoSlugField)
@classmethod @classmethod
def test_formfield_magic(cls): def test_formfield_unique(cls):
cls._test_formfield(LocalizedMagicSlugField) cls._test_formfield(LocalizedUniqueSlugField)
@staticmethod @staticmethod
def _test_populate(model): def _test_populate(model):
@@ -130,7 +179,7 @@ class LocalizedSlugFieldTestCase(TestCase):
def _test_unique_slug(model): def _test_unique_slug(model):
"""Tests whether unique slugs are properly generated.""" """Tests whether unique slugs are properly generated."""
title = 'mymagictitle' title = 'myuniquetitle'
obj = model() obj = model()
obj.title.en = title obj.title.en = title