# -*- coding: utf-8 -*- """ Django Translatable Fields - Context Management This module provides context managers and utilities for temporarily overriding language context when working with translatable fields. It allows you to bypass Django's current language setting and force specific language contexts for translatable field operations. Key Features: - Thread-safe language context storage - Context managers for temporary language overrides - Integration with Django's language activation system - Support for nested context operations - Automatic cleanup and restoration Context Hierarchy (highest to lowest priority): 1. Explicit language parameter in method calls 2. QuerySet context (with_context) 3. Global context manager (translatable_context) 4. Django's current language (get_language) 5. Default fallback ('en_US') Usage: # Global context for code blocks with translatable_context('de'): products = Product.objects.search('test') # Uses German # Django language context with django_language_context('fr'): # Django's get_language() returns 'fr' pass Author: Holger Sielaff Version: 0.1.0 """ from contextlib import contextmanager from django.utils.translation import get_language, activate, deactivate import threading # Thread-local storage for context overrides _context_storage = threading.local() class TranslatableContext: """ Context manager for temporarily setting language context for translatable operations """ def __init__(self, language): self.language = language self.previous_language = None def __enter__(self): self.previous_language = getattr(_context_storage, 'language', None) _context_storage.language = self.language return self def __exit__(self, exc_type, exc_val, exc_tb): if self.previous_language is not None: _context_storage.language = self.previous_language else: if hasattr(_context_storage, 'language'): delattr(_context_storage, 'language') return False @contextmanager def translatable_context(language): """ Context manager for temporarily overriding language context. Args: language: Language code to use Example: with translatable_context('de'): products = Product.objects.search('moep') # Searches in German for product in products: print(product.name) # Shows German name """ with TranslatableContext(language): yield @contextmanager def django_language_context(language): """ Context manager that temporarily changes Django's active language. Args: language: Language code to activate Example: with django_language_context('de'): # Django's get_language() will return 'de' products = Product.objects.search('moep') """ previous_language = get_language() try: activate(language) yield finally: if previous_language: activate(previous_language) else: deactivate() def get_context_language(): """ Get the current context language override, if any. Returns: Language code from context, or None if no override """ return getattr(_context_storage, 'language', None) def get_effective_language(explicit_language=None): """ Get the effective language to use for translatable operations. Priority: 1. Explicit language parameter 2. Context language override 3. Django's current language 4. Default fallback (en_US) Args: explicit_language: Explicitly specified language Returns: Language code to use """ if explicit_language is not None: return explicit_language context_lang = get_context_language() if context_lang is not None: return context_lang django_lang = get_language() if django_lang: return django_lang return 'en_US' class LanguageContextMixin: """ Mixin that adds context language awareness to QuerySets and Managers """ def _get_effective_language(self, language=None): """Get effective language considering all sources""" return get_effective_language(language) # Decorator for functions that should respect language context def with_language_context(func): """ Decorator that makes a function respect the current language context. Example: @with_language_context def get_product_name(product): return product.name # Will use context language if set """ def wrapper(*args, **kwargs): # If 'language' not explicitly provided, inject context language if 'language' not in kwargs: context_lang = get_context_language() if context_lang: kwargs['language'] = context_lang return func(*args, **kwargs) return wrapper