diff --git a/localized_fields/fields/autoslug_field.py b/localized_fields/fields/autoslug_field.py index 537aa46..0c07815 100644 --- a/localized_fields/fields/autoslug_field.py +++ b/localized_fields/fields/autoslug_field.py @@ -1,12 +1,14 @@ -from typing import Callable, Tuple +from typing import Callable, Tuple, Union from datetime import datetime from django import forms from django.conf import settings +from django.utils import translation from django.utils.text import slugify from .field import LocalizedField from ..value import LocalizedValue +from ..util import resolve_object_property class LocalizedAutoSlugField(LocalizedField): @@ -147,7 +149,7 @@ class LocalizedAutoSlugField(LocalizedField): ] @staticmethod - def _get_populate_from_value(instance, field_name: str, language: str): + def _get_populate_from_value(instance, field_name: Union[str, Tuple[str]], language: str): """Gets the value to create a slug from in the specified language. Arguments: @@ -164,5 +166,17 @@ class LocalizedAutoSlugField(LocalizedField): The text to generate a slug for. """ - value = getattr(instance, field_name, None) - return value.get(language) + def get_field_value(name): + value = resolve_object_property(instance, name) + with translation.override(language): + return str(value) + + if isinstance(field_name, tuple) or isinstance(field_name, list): + value = '-'.join([ + value + for value in [get_field_value(name) for name in field_name] + if value + ]) + return value + + return get_field_value(field_name) diff --git a/localized_fields/util.py b/localized_fields/util.py index 35d1ff2..ac9a722 100644 --- a/localized_fields/util.py +++ b/localized_fields/util.py @@ -19,3 +19,26 @@ def get_language_codes() -> List[str]: lang_code for lang_code, _ in settings.LANGUAGES ] + + +def resolve_object_property(obj, path: str): + """Resolves the value of a property on an object. + + Is able to resolve nested properties. For example, + a path can be specified: + + 'other.beer.name' + + Raises: + AttributeError: + In case the property could not be resolved. + + Returns: + The value of the specified property. + """ + + value = obj + for path_part in path.split('.'): + value = getattr(value, path_part) + + return value diff --git a/tests/test_slug_fields.py b/tests/test_slug_fields.py index a2f25ad..2c563e2 100644 --- a/tests/test_slug_fields.py +++ b/tests/test_slug_fields.py @@ -1,6 +1,7 @@ import copy from django import forms +from django.db import models from django.conf import settings from django.test import TestCase from django.db.utils import IntegrityError @@ -31,6 +32,7 @@ class LocalizedSlugFieldTestCase(TestCase): 'LocalizedAutoSlugFieldTestModel', { 'title': LocalizedField(), + 'name': models.CharField(max_length=255), 'slug': LocalizedAutoSlugField(populate_from='title') } ) @@ -39,6 +41,7 @@ class LocalizedSlugFieldTestCase(TestCase): 'LocalizedUniqueSlugFieldTestModel', { 'title': LocalizedField(), + 'name': models.CharField(max_length=255), 'slug': LocalizedUniqueSlugField(populate_from='title') } ) @@ -155,6 +158,22 @@ class LocalizedSlugFieldTestCase(TestCase): def test_formfield_unique(cls): cls._test_formfield(LocalizedUniqueSlugField) + @classmethod + def test_populate_multiple_from_fields_auto(cls): + cls._test_populate_multiple_from_fields(LocalizedAutoSlugField) + + @classmethod + def test_populate_multiple_from_fields_unique(cls): + cls._test_populate_multiple_from_fields(LocalizedUniqueSlugField) + + @classmethod + def test_populate_multiple_from_fields_fk_auto(cls): + cls._test_populate_multiple_from_fields_fk(LocalizedAutoSlugField) + + @classmethod + def test_populate_multiple_from_fields_fk_unique(cls): + cls._test_populate_multiple_from_fields_fk(LocalizedUniqueSlugField) + @staticmethod def _test_populate(model): """Tests whether the populating feature works correctly.""" @@ -165,6 +184,67 @@ class LocalizedSlugFieldTestCase(TestCase): assert obj.slug.get('en') == slugify(obj.title) + @staticmethod + def _test_populate_multiple_from_fields(field_type): + """Tests whether populating the slug from multiple + fields works correctly.""" + + model = get_fake_model( + '_test_populate_multiple_from_fields_' + str(field_type), + { + 'title': LocalizedField(), + 'name': models.CharField(max_length=255), + 'slug': field_type(populate_from=('title', 'name')) + } + ) + + obj = model() + for lang_code, lang_name in settings.LANGUAGES: + obj.name = 'swen' + obj.title.set(lang_code, 'title %s' % lang_name) + + obj.save() + + for lang_code, lang_name in settings.LANGUAGES: + assert obj.slug.get(lang_code) == 'title-%s-swen' % lang_name.lower() + + @staticmethod + def _test_populate_multiple_from_fields_fk(field_type): + """Tests whether populating the slug from multiple + fields works correctly.""" + + model_fk = get_fake_model( + '_test_populate_multiple_from_fields_fk_other_' + str(field_type), + { + 'name': LocalizedField(), + } + ) + + model = get_fake_model( + '_test_populate_multiple_from_fields_fk_' + str(field_type), + { + 'title': LocalizedField(), + 'other': models.ForeignKey(model_fk), + 'slug': field_type(populate_from=('title', 'other.name')) + } + ) + + other = model_fk.objects.create(name={settings.LANGUAGE_CODE: 'swen'}) + # for lang_code, lang_name in settings.LANGUAGES: + # other.name.set(lang_code, 'swen') + + # other.save() + + obj = model() + for lang_code, lang_name in settings.LANGUAGES: + obj.other_id = other.id + obj.title.set(lang_code, 'title %s' % lang_name) + + obj.save() + + for lang_code, lang_name in settings.LANGUAGES: + assert obj.slug.get(lang_code) == 'title-%s-swen' % lang_name.lower() + @staticmethod def _test_populate_multiple_languages(model): """Tests whether the populating feature correctly