18 Commits
v6.0 ... v6.5

Author SHA1 Message Date
Swen Kooij
0f30cc1493 Ignore build/ folder 2021-11-08 14:39:32 +02:00
Swen Kooij
0d1e9510cf Fix broken Django icon in README 2021-11-08 14:39:20 +02:00
Swen Kooij
25c1c24ccb Declare support for Django 3.2 and Python 3.9 2021-11-08 14:38:01 +02:00
Swen Kooij
bd3005a7e9 Bump version number to 6.5 2021-11-08 14:36:55 +02:00
Swen Kooij
7902d8225a Do not set default_app_config for Django 3.2 and newer
See: See: https://docs.djangoproject.com/en/3.2/releases/3.2/#what-s-new-in-django-3-2
2021-11-08 14:36:45 +02:00
Swen Kooij
f024e4feb5 Bump version to v6.4 2021-03-22 07:47:23 +02:00
Swen Kooij
92cb5e8b1f Add LocalizedValue.is_empty() 2021-03-22 07:47:00 +02:00
Swen Kooij
5c298ef13e Bump version number to 6.3 2021-03-13 14:01:51 +02:00
Swen Kooij
1b3e5989d3 LocalizedUniqueSlugField should properly deconstruct 'enabled' flag 2021-03-13 13:45:22 +02:00
Swen Kooij
d57f9a41bb Mark pytest as a third-party library for isort
Not sure why it doesn't get that.
2021-03-13 13:32:01 +02:00
Swen Kooij
bd8924224e Add flag to disable LocalizedUniqueSlugField 2021-03-13 13:24:36 +02:00
Swen Kooij
62e1e805c7 Bump version number to v6.2 2020-12-07 10:49:36 +02:00
Swen Kooij
afc39745bf Bump version number to 6.2rc1 2020-11-30 12:28:03 +02:00
Swen Kooij
1406954dec Merge pull request #91 from SectorLabs/immutable-slugs
Add a flag to make LocalizedUniqueSlugField immutable
2020-11-30 12:26:47 +02:00
Swen Kooij
afb94ecf66 Add a flag to make LocalizedUniqueSlugField immutable 2020-11-27 16:36:23 +02:00
Swen Kooij
7ba0ff60ec Bump version number to 6.1 2020-11-25 13:27:11 +02:00
Swen Kooij
63fb79b02b Merge pull request #90 from SectorLabs/prevent-memory-leak
Prevent accumulating redundant data
2020-11-25 13:24:41 +02:00
Cristi Ingineru
8ed09f712d Prevent accumulating redundant data 2020-11-25 12:00:11 +02:00
9 changed files with 108 additions and 9 deletions

1
.gitignore vendored
View File

@@ -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

View File

@@ -3,7 +3,7 @@
| :white_check_mark: | **Tests** | [![CircleCI](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master.svg?style=svg)](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master) | | :white_check_mark: | **Tests** | [![CircleCI](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master.svg?style=svg)](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master) |
| :memo: | **License** | [![License](https://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org) | | :memo: | **License** | [![License](https://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org) |
| :package: | **PyPi** | [![PyPi](https://badge.fury.io/py/django-localized-fields.svg)](https://pypi.python.org/pypi/django-localized-fields) | | :package: | **PyPi** | [![PyPi](https://badge.fury.io/py/django-localized-fields.svg)](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)

View File

@@ -1 +1,4 @@
default_app_config = "localized_fields.apps.LocalizedFieldsConfig" import django
if django.VERSION < (3, 2):
default_app_config = "localized_fields.apps.LocalizedFieldsConfig"

View File

@@ -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]

View File

@@ -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."""

View File

@@ -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

View File

@@ -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",
], ],

View File

@@ -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

View File

@@ -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