diff --git a/modeltranslation/fields.py b/modeltranslation/fields.py index 7b5ca82..15b09d6 100644 --- a/modeltranslation/fields.py +++ b/modeltranslation/fields.py @@ -4,6 +4,7 @@ from django.core.exceptions import ImproperlyConfigured from django.db.models import fields from modeltranslation import settings as mt_settings +from modeltranslation.thread_context import fallbacks_enabled from modeltranslation.utils import ( get_language, build_localized_fieldname, @@ -350,7 +351,7 @@ class TranslationFieldDescriptor(object): val = getattr(instance, loc_field_name, None) if self.meaningful_value(val, undefined): return val - if mt_settings.ENABLE_FALLBACKS and self.fallback_value is not NONE: + if fallbacks_enabled() and self.fallback_value is not NONE: return self.fallback_value else: if default is NONE: diff --git a/modeltranslation/manager.py b/modeltranslation/manager.py index d0a5712..a17a815 100644 --- a/modeltranslation/manager.py +++ b/modeltranslation/manager.py @@ -17,8 +17,8 @@ from django.db.models.query import QuerySet, ValuesIterable from django.db.models.utils import create_namedtuple_class from django.utils.tree import Node -from modeltranslation import settings from modeltranslation.fields import TranslationField +from modeltranslation.thread_context import auto_populate_mode from modeltranslation.utils import ( auto_populate, build_localized_fieldname, @@ -376,7 +376,7 @@ class MultilingualQuerySet(QuerySet): def _populate_mode(self): # Populate can be set using a global setting or a manager method. if self._populate is None: - return settings.AUTO_POPULATE + return auto_populate_mode() return self._populate # This method was not present in django-linguo diff --git a/modeltranslation/thread_context.py b/modeltranslation/thread_context.py new file mode 100644 index 0000000..4b89d4e --- /dev/null +++ b/modeltranslation/thread_context.py @@ -0,0 +1,48 @@ +import threading +from typing import Union +from typing_extensions import Literal + +from modeltranslation import settings + + +AutoPopulate = Union[bool, Literal["all", "default", "required"]] + + +class ModelTranslationThreadLocal(threading.local): + """Holds thread-local data for modeltranslation.""" + + auto_populate: Union[AutoPopulate, None] = None + enable_fallbacks: Union[bool, None] = None + + +_mt_thread_context = ModelTranslationThreadLocal() + + +def set_auto_populate(value: Union[AutoPopulate, None]) -> None: + """Set the auto_populate for the current thread.""" + _mt_thread_context.auto_populate = value + + +def set_enable_fallbacks(value: Union[bool, None]) -> None: + """Set the enable_fallbacks for the current thread.""" + _mt_thread_context.enable_fallbacks = value + + +def auto_populate_mode() -> AutoPopulate: + """Return the auto_populate mode for the current thread.""" + auto_populate = _mt_thread_context.auto_populate + + if auto_populate is not None: + return auto_populate + + return settings.AUTO_POPULATE + + +def fallbacks_enabled() -> bool: + """Return whether fallbacks are enabled for the current thread.""" + enable_fallbacks = _mt_thread_context.enable_fallbacks + + if enable_fallbacks is not None: + return enable_fallbacks + + return settings.ENABLE_FALLBACKS diff --git a/modeltranslation/translator.py b/modeltranslation/translator.py index e685f0c..e1d7c6a 100644 --- a/modeltranslation/translator.py +++ b/modeltranslation/translator.py @@ -22,6 +22,7 @@ from modeltranslation.manager import ( rewrite_lookup_key, append_translated, ) +from modeltranslation.thread_context import auto_populate_mode from modeltranslation.utils import build_localized_fieldname, parse_field @@ -389,7 +390,7 @@ def populate_translation_fields(sender, kwargs): defined (for example to make lookups / filtering without resorting to query fallbacks). """ - populate = mt_settings.AUTO_POPULATE + populate = auto_populate_mode() if not populate: return if populate is True: diff --git a/modeltranslation/utils.py b/modeltranslation/utils.py index cfee6c4..fb5af92 100644 --- a/modeltranslation/utils.py +++ b/modeltranslation/utils.py @@ -6,6 +6,11 @@ from django.utils.translation import get_language_info from django.utils.functional import lazy from modeltranslation import settings +from modeltranslation.thread_context import ( + set_auto_populate, + set_enable_fallbacks, + fallbacks_enabled, +) def get_language(): @@ -112,8 +117,9 @@ def resolution_order(lang, override=None): First is always the parameter language, later are fallback languages. Override parameter has priority over FALLBACK_LANGUAGES. """ - if not settings.ENABLE_FALLBACKS: + if not fallbacks_enabled(): return (lang,) + if override is None: override = {} fallback_for_lang = override.get(lang, settings.FALLBACK_LANGUAGES.get(lang, ())) @@ -141,12 +147,11 @@ def auto_populate(mode='all'): with auto_populate('required'): call_command('loaddata', 'fixture.json') """ - current_population_mode = settings.AUTO_POPULATE - settings.AUTO_POPULATE = mode + set_auto_populate(mode) try: yield finally: - settings.AUTO_POPULATE = current_population_mode + set_auto_populate(None) @contextmanager @@ -163,12 +168,11 @@ def fallbacks(enable=True): processing or check if there is a value for the current language (not knowing the language) """ - current_enable_fallbacks = settings.ENABLE_FALLBACKS - settings.ENABLE_FALLBACKS = enable + set_enable_fallbacks(enable) try: yield finally: - settings.ENABLE_FALLBACKS = current_enable_fallbacks + set_enable_fallbacks(None) def parse_field(setting, field_name, default):