Initial
This commit is contained in:
178
django_translatable_fields/context.py
Normal file
178
django_translatable_fields/context.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# -*- 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 <holger@backender.de>
|
||||
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
|
||||
Reference in New Issue
Block a user