mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-10-21 23:28:58 +03:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ad2ef34546 | ||
|
1317023160 | ||
|
ca6b1c88fa | ||
|
64c3c06612 | ||
|
b121dfc2d7 | ||
|
d529da8886 | ||
|
ca879087ea | ||
|
302a64a02c | ||
|
bb11253207 | ||
|
5db87763fb | ||
|
759d03133b | ||
|
d5ed3ced40 |
46
README.rst
46
README.rst
@@ -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,8 +246,8 @@ 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):
|
||||||
|
@@ -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'
|
||||||
]
|
]
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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',
|
||||||
]
|
]
|
||||||
|
@@ -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."""
|
||||||
|
|
||||||
|
@@ -1,12 +1,16 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.text import slugify
|
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_value import LocalizedValue
|
||||||
from .localized_autoslug_field import LocalizedAutoSlugField
|
from .localized_autoslug_field import LocalizedAutoSlugField
|
||||||
from ..util import get_language_codes
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class LocalizedMagicSlugField(LocalizedAutoSlugField):
|
class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
||||||
"""Automatically provides slugs for a localized
|
"""Automatically provides slugs for a localized
|
||||||
field upon saving."
|
field upon saving."
|
||||||
|
|
||||||
@@ -17,19 +21,35 @@ class LocalizedMagicSlugField(LocalizedAutoSlugField):
|
|||||||
- Improved performance
|
- Improved performance
|
||||||
|
|
||||||
When in doubt, use this over :see:LocalizedAutoSlugField.
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initializes a new instance of :see:LocalizedMagicSlugField."""
|
"""Initializes a new instance of :see:LocalizedUniqueSlugField."""
|
||||||
|
|
||||||
self.populate_from = kwargs.pop('populate_from')
|
|
||||||
kwargs['uniqueness'] = kwargs.pop('uniqueness', get_language_codes())
|
kwargs['uniqueness'] = kwargs.pop('uniqueness', get_language_codes())
|
||||||
|
|
||||||
super(LocalizedAutoSlugField, self).__init__(
|
self.include_time = kwargs.pop('include_time', False)
|
||||||
|
|
||||||
|
super(LocalizedUniqueSlugField, self).__init__(
|
||||||
*args,
|
*args,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.populate_from = kwargs.pop('populate_from')
|
||||||
|
|
||||||
|
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):
|
def pre_save(self, instance, add: bool):
|
||||||
"""Ran just before the model is saved, allows us to built
|
"""Ran just before the model is saved, allows us to built
|
||||||
the slug.
|
the slug.
|
||||||
@@ -46,6 +66,12 @@ class LocalizedMagicSlugField(LocalizedAutoSlugField):
|
|||||||
The localized slug that was generated.
|
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()
|
slugs = LocalizedValue()
|
||||||
|
|
||||||
for lang_code, _ in settings.LANGUAGES:
|
for lang_code, _ in settings.LANGUAGES:
|
||||||
@@ -59,6 +85,9 @@ class LocalizedMagicSlugField(LocalizedAutoSlugField):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
slug = slugify(value, allow_unicode=True)
|
slug = slugify(value, allow_unicode=True)
|
||||||
|
if self.include_time:
|
||||||
|
slug += '-%d' % datetime.now().microsecond
|
||||||
|
|
||||||
if instance.retries > 0:
|
if instance.retries > 0:
|
||||||
slug += '-%d' % instance.retries
|
slug += '-%d' % instance.retries
|
||||||
|
|
@@ -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."""
|
||||||
|
|
||||||
|
38
localized_fields/mixins.py
Normal file
38
localized_fields/mixins.py
Normal 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()
|
@@ -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()
|
|
||||||
|
2
setup.py
2
setup.py
@@ -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',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
license='MIT License',
|
license='MIT License',
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -5,7 +5,7 @@ from django.db.utils import IntegrityError
|
|||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from localized_fields import (LocalizedField, LocalizedAutoSlugField,
|
from localized_fields import (LocalizedField, LocalizedAutoSlugField,
|
||||||
LocalizedMagicSlugField)
|
LocalizedUniqueSlugField)
|
||||||
|
|
||||||
from .fake_model import get_fake_model
|
from .fake_model import get_fake_model
|
||||||
|
|
||||||
@@ -31,10 +31,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 +43,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 +51,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 +59,35 @@ 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)
|
||||||
|
|
||||||
|
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 +104,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 +112,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 +151,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
|
||||||
|
Reference in New Issue
Block a user