mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-10-27 01:18:57 +03:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f30cc1493 | ||
|
|
0d1e9510cf | ||
|
|
25c1c24ccb | ||
|
|
bd3005a7e9 | ||
|
|
7902d8225a | ||
|
|
f024e4feb5 | ||
|
|
92cb5e8b1f | ||
|
|
5c298ef13e | ||
|
|
1b3e5989d3 | ||
|
|
d57f9a41bb | ||
|
|
bd8924224e | ||
|
|
62e1e805c7 | ||
|
|
afc39745bf | ||
|
|
1406954dec | ||
|
|
afb94ecf66 | ||
|
|
7ba0ff60ec | ||
|
|
63fb79b02b | ||
|
|
8ed09f712d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ reports/
|
|||||||
# Ignore build results
|
# Ignore build results
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
dist/
|
dist/
|
||||||
|
build/
|
||||||
pip-wheel-metadata
|
pip-wheel-metadata
|
||||||
|
|
||||||
# Ignore stupid .DS_Store
|
# Ignore stupid .DS_Store
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
| :white_check_mark: | **Tests** | [](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master) |
|
| :white_check_mark: | **Tests** | [](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master) |
|
||||||
| :memo: | **License** | [](http://doge.mit-license.org) |
|
| :memo: | **License** | [](http://doge.mit-license.org) |
|
||||||
| :package: | **PyPi** | [](https://pypi.python.org/pypi/django-localized-fields) |
|
| :package: | **PyPi** | [](https://pypi.python.org/pypi/django-localized-fields) |
|
||||||
| <img src="https://icon-library.net/images/django-icon/django-icon-0.jpg" width="22px" height="22px" align="center" /> | **Django Versions** | 2.0, 2.1, 2.2, 3.0, 3.1 |
|
| <img src="https://cdn.iconscout.com/icon/free/png-256/django-1-282754.png" width="22px" height="22px" align="center" /> | **Django Versions** | 2.0, 2.1, 2.2, 3.0, 3.1, 3.2 |
|
||||||
| <img src="http://www.iconarchive.com/download/i73027/cornmanthe3rd/plex/Other-python.ico" width="22px" height="22px" align="center" /> | **Python Versions** | 3.6, 3.7, 3.8, 3.9 |
|
| <img src="http://www.iconarchive.com/download/i73027/cornmanthe3rd/plex/Other-python.ico" width="22px" height="22px" align="center" /> | **Python Versions** | 3.6, 3.7, 3.8, 3.9 |
|
||||||
| :book: | **Documentation** | [Read The Docs](https://django-localized-fields.readthedocs.io) |
|
| :book: | **Documentation** | [Read The Docs](https://django-localized-fields.readthedocs.io) |
|
||||||
| :warning: | **Upgrade** | [Upgrade fom v5.x](https://django-localized-fields.readthedocs.io/en/latest/releases.html#v6-0)
|
| :warning: | **Upgrade** | [Upgrade fom v5.x](https://django-localized-fields.readthedocs.io/en/latest/releases.html#v6-0)
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
default_app_config = "localized_fields.apps.LocalizedFieldsConfig"
|
import django
|
||||||
|
|
||||||
|
if django.VERSION < (3, 2):
|
||||||
|
default_app_config = "localized_fields.apps.LocalizedFieldsConfig"
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
|||||||
When in doubt, use this over :see:LocalizedAutoSlugField.
|
When in doubt, use this over :see:LocalizedAutoSlugField.
|
||||||
Inherit from :see:AtomicSlugRetryMixin in your model to
|
Inherit from :see:AtomicSlugRetryMixin in your model to
|
||||||
make this field work properly.
|
make this field work properly.
|
||||||
|
|
||||||
|
By default, this creates a new slug if the field(s) specified
|
||||||
|
in `populate_from` are changed. Set `immutable=True` to get
|
||||||
|
immutable slugs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -28,6 +32,9 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
|||||||
|
|
||||||
kwargs["uniqueness"] = kwargs.pop("uniqueness", get_language_codes())
|
kwargs["uniqueness"] = kwargs.pop("uniqueness", get_language_codes())
|
||||||
|
|
||||||
|
self.enabled = kwargs.pop("enabled", True)
|
||||||
|
self.immutable = kwargs.pop("immutable", False)
|
||||||
|
|
||||||
super(LocalizedUniqueSlugField, self).__init__(*args, **kwargs)
|
super(LocalizedUniqueSlugField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.populate_from = kwargs.pop("populate_from")
|
self.populate_from = kwargs.pop("populate_from")
|
||||||
@@ -42,6 +49,13 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
|||||||
|
|
||||||
kwargs["populate_from"] = self.populate_from
|
kwargs["populate_from"] = self.populate_from
|
||||||
kwargs["include_time"] = self.include_time
|
kwargs["include_time"] = self.include_time
|
||||||
|
|
||||||
|
if self.enabled is False:
|
||||||
|
kwargs["enabled"] = self.enabled
|
||||||
|
|
||||||
|
if self.immutable is True:
|
||||||
|
kwargs["immutable"] = self.immutable
|
||||||
|
|
||||||
return name, path, args, kwargs
|
return name, path, args, kwargs
|
||||||
|
|
||||||
def pre_save(self, instance, add: bool):
|
def pre_save(self, instance, add: bool):
|
||||||
@@ -59,6 +73,9 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
|||||||
The localized slug that was generated.
|
The localized slug that was generated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not self.enabled:
|
||||||
|
return getattr(instance, self.name)
|
||||||
|
|
||||||
if not isinstance(instance, AtomicSlugRetryMixin):
|
if not isinstance(instance, AtomicSlugRetryMixin):
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
(
|
(
|
||||||
@@ -76,10 +93,14 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
|||||||
|
|
||||||
slug = slugify(value, allow_unicode=True)
|
slug = slugify(value, allow_unicode=True)
|
||||||
|
|
||||||
|
current_slug = getattr(instance, self.name).get(lang_code)
|
||||||
|
if current_slug and self.immutable:
|
||||||
|
slugs.set(lang_code, current_slug)
|
||||||
|
continue
|
||||||
|
|
||||||
# verify whether it's needed to re-generate a slug,
|
# verify whether it's needed to re-generate a slug,
|
||||||
# if not, re-use the same slug
|
# if not, re-use the same slug
|
||||||
if instance.pk is not None:
|
if instance.pk is not None:
|
||||||
current_slug = getattr(instance, self.name).get(lang_code)
|
|
||||||
if current_slug is not None:
|
if current_slug is not None:
|
||||||
current_slug_end_index = current_slug.rfind("-")
|
current_slug_end_index = current_slug.rfind("-")
|
||||||
stripped_slug = current_slug[0:current_slug_end_index]
|
stripped_slug = current_slug[0:current_slug_end_index]
|
||||||
|
|||||||
@@ -128,15 +128,23 @@ class LocalizedValue(dict):
|
|||||||
target_languages = fallback_config.get(
|
target_languages = fallback_config.get(
|
||||||
target_language, [settings.LANGUAGE_CODE]
|
target_language, [settings.LANGUAGE_CODE]
|
||||||
)
|
)
|
||||||
target_languages.insert(0, target_language)
|
|
||||||
|
|
||||||
for lang_code in target_languages:
|
for lang_code in [target_language] + target_languages:
|
||||||
value = self.get(lang_code)
|
value = self.get(lang_code)
|
||||||
if value:
|
if value:
|
||||||
return value or None
|
return value or None
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def is_empty(self) -> bool:
|
||||||
|
"""Gets whether all the languages contain the default value."""
|
||||||
|
|
||||||
|
for lang_code, _ in settings.LANGUAGES:
|
||||||
|
if self.get(lang_code) != self.default_value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Gets the value in the current language or falls back to the next
|
"""Gets the value in the current language or falls back to the next
|
||||||
language if there's no value in the current language."""
|
language if there's no value in the current language."""
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ lines_between_types=1
|
|||||||
include_trailing_comma=True
|
include_trailing_comma=True
|
||||||
not_skip=__init__.py
|
not_skip=__init__.py
|
||||||
known_standard_library=dataclasses
|
known_standard_library=dataclasses
|
||||||
known_third_party=django_bleach,bleach
|
known_third_party=django_bleach,bleach,pytest
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -36,7 +36,7 @@ with open(
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="django-localized-fields",
|
name="django-localized-fields",
|
||||||
version="6.0",
|
version="6.5",
|
||||||
packages=find_packages(exclude=["tests"]),
|
packages=find_packages(exclude=["tests"]),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
license="MIT License",
|
license="MIT License",
|
||||||
@@ -64,6 +64,9 @@ setup(
|
|||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
"Topic :: Internet :: WWW/HTTP",
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -215,6 +217,53 @@ class LocalizedSlugFieldTestCase(TestCase):
|
|||||||
for lang_code, lang_name in settings.LANGUAGES:
|
for lang_code, lang_name in settings.LANGUAGES:
|
||||||
assert obj.slug.get(lang_code) == "title-%s" % lang_name.lower()
|
assert obj.slug.get(lang_code) == "title-%s" % lang_name.lower()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def test_disable(cls):
|
||||||
|
"""Tests whether disabling auto-slugging works."""
|
||||||
|
|
||||||
|
Model = get_fake_model(
|
||||||
|
{
|
||||||
|
"title": LocalizedField(),
|
||||||
|
"slug": LocalizedUniqueSlugField(
|
||||||
|
populate_from="title", enabled=False
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
obj = Model()
|
||||||
|
obj.title = "test"
|
||||||
|
|
||||||
|
# should raise IntegrityError because auto-slugging
|
||||||
|
# is disabled and the slug field is NULL
|
||||||
|
with pytest.raises(IntegrityError):
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def test_allows_override_when_immutable(cls):
|
||||||
|
"""Tests whether setting a value manually works and does not get
|
||||||
|
overriden."""
|
||||||
|
|
||||||
|
Model = get_fake_model(
|
||||||
|
{
|
||||||
|
"title": LocalizedField(),
|
||||||
|
"name": models.CharField(max_length=255),
|
||||||
|
"slug": LocalizedUniqueSlugField(
|
||||||
|
populate_from="title", immutable=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
obj = Model()
|
||||||
|
|
||||||
|
for lang_code, lang_name in settings.LANGUAGES:
|
||||||
|
obj.slug.set(lang_code, "my value %s" % lang_code)
|
||||||
|
obj.title.set(lang_code, "my title %s" % lang_code)
|
||||||
|
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
for lang_code, lang_name in settings.LANGUAGES:
|
||||||
|
assert obj.slug.get(lang_code) == "my value %s" % lang_code
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def test_unique_slug(cls):
|
def test_unique_slug(cls):
|
||||||
"""Tests whether unique slugs are properly generated."""
|
"""Tests whether unique slugs are properly generated."""
|
||||||
@@ -248,10 +297,13 @@ class LocalizedSlugFieldTestCase(TestCase):
|
|||||||
"""Tests whether the :see:deconstruct function properly retains options
|
"""Tests whether the :see:deconstruct function properly retains options
|
||||||
specified in the constructor."""
|
specified in the constructor."""
|
||||||
|
|
||||||
field = LocalizedUniqueSlugField(populate_from="title")
|
field = LocalizedUniqueSlugField(
|
||||||
|
enabled=False, immutable=True, populate_from="title"
|
||||||
|
)
|
||||||
_, _, _, kwargs = field.deconstruct()
|
_, _, _, kwargs = field.deconstruct()
|
||||||
|
|
||||||
assert "populate_from" in kwargs
|
assert not kwargs["enabled"]
|
||||||
|
assert kwargs["immutable"]
|
||||||
assert kwargs["populate_from"] == field.populate_from
|
assert kwargs["populate_from"] == field.populate_from
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ class LocalizedValueTestCase(TestCase):
|
|||||||
for lang_code, _ in settings.LANGUAGES:
|
for lang_code, _ in settings.LANGUAGES:
|
||||||
assert getattr(value, lang_code) is None
|
assert getattr(value, lang_code) is None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def test_is_empty():
|
||||||
|
"""Tests whether a newly constructed :see:LocalizedValue without any
|
||||||
|
content is considered "empty"."""
|
||||||
|
|
||||||
|
value = LocalizedValue()
|
||||||
|
assert value.is_empty()
|
||||||
|
|
||||||
|
value.set(settings.LANGUAGE_CODE, "my value")
|
||||||
|
assert not value.is_empty()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def test_init_array():
|
def test_init_array():
|
||||||
"""Tests whether the __init__ function of :see:LocalizedValue properly
|
"""Tests whether the __init__ function of :see:LocalizedValue properly
|
||||||
|
|||||||
Reference in New Issue
Block a user