mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-10-22 07:38:57 +03:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8c7d0773f7 | ||
|
cc911d4909 | ||
|
0f30cc1493 | ||
|
0d1e9510cf | ||
|
25c1c24ccb | ||
|
bd3005a7e9 | ||
|
7902d8225a | ||
|
f024e4feb5 | ||
|
92cb5e8b1f | ||
|
5c298ef13e | ||
|
1b3e5989d3 | ||
|
d57f9a41bb | ||
|
bd8924224e | ||
|
62e1e805c7 | ||
|
afc39745bf | ||
|
1406954dec | ||
|
afb94ecf66 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ reports/
|
||||
# Ignore build results
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
pip-wheel-metadata
|
||||
|
||||
# Ignore stupid .DS_Store
|
||||
|
@@ -3,7 +3,7 @@
|
||||
| :white_check_mark: | **Tests** | [](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master) |
|
||||
| :memo: | **License** | [](http://doge.mit-license.org) |
|
||||
| :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 |
|
||||
| :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)
|
||||
|
@@ -1 +1,4 @@
|
||||
default_app_config = "localized_fields.apps.LocalizedFieldsConfig"
|
||||
import django
|
||||
|
||||
if django.VERSION < (3, 2):
|
||||
default_app_config = "localized_fields.apps.LocalizedFieldsConfig"
|
||||
|
@@ -16,14 +16,14 @@ from .field import LocalizedField
|
||||
class LocalizedAutoSlugField(LocalizedField):
|
||||
"""Automatically provides slugs for a localized field upon saving."""
|
||||
|
||||
warnings.warn(
|
||||
"LocalizedAutoSlug is deprecated and will be removed in the next major version.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initializes a new instance of :see:LocalizedAutoSlugField."""
|
||||
|
||||
warnings.warn(
|
||||
"LocalizedAutoSlug is deprecated and will be removed in the next major version.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
self.populate_from = kwargs.pop("populate_from", None)
|
||||
self.include_time = kwargs.pop("include_time", False)
|
||||
|
||||
|
@@ -21,6 +21,10 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
||||
When in doubt, use this over :see:LocalizedAutoSlugField.
|
||||
Inherit from :see:AtomicSlugRetryMixin in your model to
|
||||
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):
|
||||
@@ -28,6 +32,9 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
||||
|
||||
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)
|
||||
|
||||
self.populate_from = kwargs.pop("populate_from")
|
||||
@@ -42,6 +49,13 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
||||
|
||||
kwargs["populate_from"] = self.populate_from
|
||||
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
|
||||
|
||||
def pre_save(self, instance, add: bool):
|
||||
@@ -59,6 +73,9 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
||||
The localized slug that was generated.
|
||||
"""
|
||||
|
||||
if not self.enabled:
|
||||
return getattr(instance, self.name)
|
||||
|
||||
if not isinstance(instance, AtomicSlugRetryMixin):
|
||||
raise ImproperlyConfigured(
|
||||
(
|
||||
@@ -76,10 +93,14 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
|
||||
|
||||
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,
|
||||
# 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:
|
||||
current_slug_end_index = current_slug.rfind("-")
|
||||
stripped_slug = current_slug[0:current_slug_end_index]
|
||||
|
@@ -136,6 +136,15 @@ class LocalizedValue(dict):
|
||||
|
||||
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:
|
||||
"""Gets the value in the current language or falls back to the next
|
||||
language if there's no value in the current language."""
|
||||
|
@@ -9,4 +9,4 @@ lines_between_types=1
|
||||
include_trailing_comma=True
|
||||
not_skip=__init__.py
|
||||
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(
|
||||
name="django-localized-fields",
|
||||
version="6.1",
|
||||
version="6.6",
|
||||
packages=find_packages(exclude=["tests"]),
|
||||
include_package_data=True,
|
||||
license="MIT License",
|
||||
@@ -64,6 +64,9 @@ setup(
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"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 :: Dynamic Content",
|
||||
],
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import copy
|
||||
|
||||
import pytest
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
@@ -215,6 +217,53 @@ class LocalizedSlugFieldTestCase(TestCase):
|
||||
for lang_code, lang_name in settings.LANGUAGES:
|
||||
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
|
||||
def test_unique_slug(cls):
|
||||
"""Tests whether unique slugs are properly generated."""
|
||||
@@ -248,10 +297,13 @@ class LocalizedSlugFieldTestCase(TestCase):
|
||||
"""Tests whether the :see:deconstruct function properly retains options
|
||||
specified in the constructor."""
|
||||
|
||||
field = LocalizedUniqueSlugField(populate_from="title")
|
||||
field = LocalizedUniqueSlugField(
|
||||
enabled=False, immutable=True, populate_from="title"
|
||||
)
|
||||
_, _, _, kwargs = field.deconstruct()
|
||||
|
||||
assert "populate_from" in kwargs
|
||||
assert not kwargs["enabled"]
|
||||
assert kwargs["immutable"]
|
||||
assert kwargs["populate_from"] == field.populate_from
|
||||
|
||||
@staticmethod
|
||||
|
@@ -38,6 +38,17 @@ class LocalizedValueTestCase(TestCase):
|
||||
for lang_code, _ in settings.LANGUAGES:
|
||||
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
|
||||
def test_init_array():
|
||||
"""Tests whether the __init__ function of :see:LocalizedValue properly
|
||||
|
Reference in New Issue
Block a user