Skip to content

Commit

Permalink
Make Context Managers (fallbacks and auto_populate) Thread Safe (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
saadmk11 committed Nov 8, 2022
1 parent 57b1c33 commit 4927b6c
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 11 deletions.
3 changes: 2 additions & 1 deletion modeltranslation/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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,
Expand Down Expand Up @@ -350,7 +351,7 @@ def __get__(self, instance, owner):
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:
Expand Down
4 changes: 2 additions & 2 deletions modeltranslation/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
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,
Expand Down Expand Up @@ -376,7 +376,7 @@ def update(self, **kwargs):
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
Expand Down
48 changes: 48 additions & 0 deletions modeltranslation/thread_context.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion modeltranslation/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
rewrite_lookup_key,
append_translated,
)
from modeltranslation.thread_context import auto_populate_mode
from modeltranslation.utils import build_localized_fieldname, parse_field


Expand Down Expand Up @@ -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:
Expand Down
18 changes: 11 additions & 7 deletions modeltranslation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
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():
Expand Down Expand Up @@ -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, ()))
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down

0 comments on commit 4927b6c

Please sign in to comment.