Initial
This commit is contained in:
313
django_translatable_fields/managers.py
Normal file
313
django_translatable_fields/managers.py
Normal file
@@ -0,0 +1,313 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Django Translatable Fields - Custom Managers and QuerySets
|
||||
|
||||
This module provides custom Django managers and querysets that add powerful
|
||||
search and filtering capabilities for translatable fields. It includes
|
||||
language-aware search functionality and context management.
|
||||
|
||||
Key Features:
|
||||
- Language-aware search across translatable fields
|
||||
- Context management for overriding browser language
|
||||
- Database-specific optimizations (PostgreSQL, MySQL, SQLite)
|
||||
- Chainable QuerySet operations with language context
|
||||
- Integration with global and local language contexts
|
||||
|
||||
Usage:
|
||||
# Add to your model
|
||||
class Product(models.Model):
|
||||
objects = TranslatableManager()
|
||||
|
||||
# Search in German regardless of browser language
|
||||
products = Product.objects.with_context(lang='de').search('test')
|
||||
|
||||
# Search specific field
|
||||
products = Product.objects.search_field('name', 'hello', 'en')
|
||||
|
||||
Author: Holger Sielaff <holger@backender.de>
|
||||
Version: 0.1.0
|
||||
"""
|
||||
|
||||
import json
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import get_language
|
||||
|
||||
|
||||
class TranslatableQuerySet(models.QuerySet):
|
||||
"""
|
||||
Enhanced QuerySet with language-aware search and filtering capabilities.
|
||||
|
||||
This QuerySet extends Django's standard QuerySet with methods specifically
|
||||
designed for working with translatable fields. It provides:
|
||||
|
||||
- Language context management (with_context)
|
||||
- Search functionality across multiple translatable fields
|
||||
- Single field search with language targeting
|
||||
- Language filtering (find records with content in specific language)
|
||||
- Integration with database-specific optimizations
|
||||
|
||||
The QuerySet maintains language context across chained operations,
|
||||
allowing you to set a language once and have all subsequent operations
|
||||
respect that context.
|
||||
|
||||
Example:
|
||||
# Set German context for all operations
|
||||
qs = Product.objects.with_context(lang='de')
|
||||
results = qs.filter(active=True).search('test').order_by('name')
|
||||
# All operations above will use German language context
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the QuerySet with language context support.
|
||||
|
||||
Args:
|
||||
*args: Positional arguments passed to parent
|
||||
**kwargs: Keyword arguments passed to parent
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
# Store context language override - None means use Django's current language
|
||||
self._context_language = None
|
||||
|
||||
def with_context(self, lang=None):
|
||||
"""
|
||||
Set language context for this QuerySet. All subsequent operations
|
||||
will use this language instead of the current Django language.
|
||||
|
||||
Args:
|
||||
lang: Language code to use as context
|
||||
|
||||
Returns:
|
||||
QuerySet with language context set
|
||||
|
||||
Example:
|
||||
# Use German context for all operations
|
||||
products = Product.objects.with_context(lang='de')
|
||||
|
||||
# Search will now use German by default
|
||||
german_products = products.search('moep') # Searches in German
|
||||
|
||||
# Chain with other operations
|
||||
Product.objects.with_context(lang='de').filter(price__lt=100).search('name', 'test')
|
||||
"""
|
||||
clone = self._clone()
|
||||
clone._context_language = lang
|
||||
return clone
|
||||
|
||||
def _get_context_language(self, language=None):
|
||||
"""
|
||||
Get the effective language to use, considering all context sources.
|
||||
|
||||
Args:
|
||||
language: Explicit language parameter
|
||||
|
||||
Returns:
|
||||
Language code to use
|
||||
"""
|
||||
if language is not None:
|
||||
return language
|
||||
if self._context_language is not None:
|
||||
return self._context_language
|
||||
|
||||
# Check global context manager
|
||||
from .context import get_context_language
|
||||
global_context = get_context_language()
|
||||
if global_context is not None:
|
||||
return global_context
|
||||
|
||||
return get_language() or 'en_US'
|
||||
|
||||
def _clone(self):
|
||||
"""Override clone to preserve context language"""
|
||||
clone = super()._clone()
|
||||
clone._context_language = getattr(self, '_context_language', None)
|
||||
return clone
|
||||
|
||||
def search_translatable(self, field_name, query, language=None):
|
||||
"""
|
||||
Search in a translatable field for the specified language.
|
||||
|
||||
Args:
|
||||
field_name: Name of the translatable field to search in
|
||||
query: Search query string
|
||||
language: Language code to search (defaults to context or current language)
|
||||
|
||||
Returns:
|
||||
QuerySet filtered by the search criteria
|
||||
|
||||
Example:
|
||||
Product.objects.search_translatable('name', 'moep', 'de')
|
||||
Product.objects.with_context(lang='de').search_translatable('name', 'moep')
|
||||
"""
|
||||
from .search import TranslatableSearch
|
||||
effective_language = self._get_context_language(language)
|
||||
return TranslatableSearch.search_translatable_field(self, field_name, query, effective_language)
|
||||
|
||||
def search_translatable_all_languages(self, field_name, query):
|
||||
"""
|
||||
Search in a translatable field across all languages.
|
||||
|
||||
Args:
|
||||
field_name: Name of the translatable field to search in
|
||||
query: Search query string
|
||||
|
||||
Returns:
|
||||
QuerySet filtered by the search criteria across all languages
|
||||
"""
|
||||
if not query:
|
||||
return self
|
||||
|
||||
# Search in the entire JSON field
|
||||
return self.filter(**{f'{field_name}__icontains': query})
|
||||
|
||||
def search_multiple_fields(self, query, language=None, fields=None):
|
||||
"""
|
||||
Search across multiple translatable fields.
|
||||
|
||||
Args:
|
||||
query: Search query string
|
||||
language: Language code to search (defaults to context or current language)
|
||||
fields: List of field names to search (defaults to all translatable fields)
|
||||
|
||||
Returns:
|
||||
QuerySet filtered by the search criteria
|
||||
|
||||
Example:
|
||||
Product.objects.search_multiple_fields('moep', 'de', ['name', 'description'])
|
||||
Product.objects.with_context(lang='de').search_multiple_fields('moep')
|
||||
"""
|
||||
from .search import TranslatableSearch
|
||||
effective_language = self._get_context_language(language)
|
||||
return TranslatableSearch.search_multiple_fields(self, query, effective_language, fields)
|
||||
|
||||
def filter_by_language(self, language=None):
|
||||
"""
|
||||
Filter objects that have content in the specified language.
|
||||
|
||||
Args:
|
||||
language: Language code to filter by (defaults to context or current language)
|
||||
|
||||
Returns:
|
||||
QuerySet filtered to objects with content in the specified language
|
||||
|
||||
Example:
|
||||
Product.objects.filter_by_language('de')
|
||||
Product.objects.with_context(lang='de').filter_by_language()
|
||||
"""
|
||||
effective_language = self._get_context_language(language)
|
||||
|
||||
# Get all translatable fields
|
||||
translatable_fields = self._get_translatable_fields()
|
||||
|
||||
q_objects = Q()
|
||||
for field_name in translatable_fields:
|
||||
# Check if field has content for this language
|
||||
field_q = Q(**{f'{field_name}__icontains': f'"{effective_language}":'})
|
||||
q_objects |= field_q
|
||||
|
||||
return self.filter(q_objects)
|
||||
|
||||
def _get_translatable_fields(self):
|
||||
"""
|
||||
Get list of translatable field names for this model.
|
||||
|
||||
Returns:
|
||||
List of field names that are translatable
|
||||
"""
|
||||
if not self.model:
|
||||
return []
|
||||
|
||||
translatable_fields = []
|
||||
for field in self.model._meta.get_fields():
|
||||
# Check if field has translatable attribute
|
||||
if hasattr(field, 'translatable') and field.translatable:
|
||||
translatable_fields.append(field.name)
|
||||
|
||||
return translatable_fields
|
||||
|
||||
|
||||
class TranslatableManager(models.Manager):
|
||||
"""
|
||||
Custom manager for models with translatable fields
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return custom QuerySet"""
|
||||
return TranslatableQuerySet(self.model, using=self._db)
|
||||
|
||||
def with_context(self, lang=None):
|
||||
"""
|
||||
Set language context for all subsequent operations.
|
||||
|
||||
Args:
|
||||
lang: Language code to use as context
|
||||
|
||||
Returns:
|
||||
QuerySet with language context set
|
||||
|
||||
Example:
|
||||
Product.objects.with_context(lang='de').search('moep')
|
||||
"""
|
||||
return self.get_queryset().with_context(lang)
|
||||
|
||||
def search(self, query, language=None, fields=None):
|
||||
"""
|
||||
Convenience method for searching translatable fields.
|
||||
|
||||
Args:
|
||||
query: Search query string
|
||||
language: Language code to search (defaults to context or current language)
|
||||
fields: List of field names to search (defaults to all translatable fields)
|
||||
|
||||
Returns:
|
||||
QuerySet filtered by the search criteria
|
||||
|
||||
Example:
|
||||
Product.objects.search('moep', 'de')
|
||||
Product.objects.with_context(lang='de').search('moep')
|
||||
"""
|
||||
return self.get_queryset().search_multiple_fields(query, language, fields)
|
||||
|
||||
def search_field(self, field_name, query, language=None):
|
||||
"""
|
||||
Convenience method for searching a specific translatable field.
|
||||
|
||||
Args:
|
||||
field_name: Name of the translatable field to search in
|
||||
query: Search query string
|
||||
language: Language code to search (defaults to context or current language)
|
||||
|
||||
Returns:
|
||||
QuerySet filtered by the search criteria
|
||||
|
||||
Example:
|
||||
Product.objects.search_field('name', 'moep', 'de')
|
||||
Product.objects.with_context(lang='de').search_field('name', 'moep')
|
||||
"""
|
||||
return self.get_queryset().search_translatable(field_name, query, language)
|
||||
|
||||
def with_language(self, language=None):
|
||||
"""
|
||||
Get objects that have content in the specified language.
|
||||
|
||||
Args:
|
||||
language: Language code to filter by (defaults to context or current language)
|
||||
|
||||
Returns:
|
||||
QuerySet filtered to objects with content in the specified language
|
||||
"""
|
||||
return self.get_queryset().filter_by_language(language)
|
||||
|
||||
|
||||
# Mixin to easily add translatable search to any model
|
||||
class TranslatableModelMixin:
|
||||
"""
|
||||
Mixin to add translatable search capabilities to any model.
|
||||
Just inherit from this mixin in your model.
|
||||
"""
|
||||
|
||||
objects = TranslatableManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
Reference in New Issue
Block a user