django-modeltranslation/modeltranslation/manager.py
wrwrwr 54699c580b Separate registry for manager shouldn't be needed any longer -- the main registry caches options for all seen models.
Now it might be possible to optimize the manager by precomputing or caching ``fields_to_translatable_models`` on translation options (instances).
2013-02-10 18:28:00 +01:00

209 lines
8.2 KiB
Python

# -*- coding: utf-8 -*-
"""
The idea of MultilingualManager is taken from
django-linguo by Zach Mathew
https://github.com/zmathew/django-linguo
"""
from django.db import models
from django.db.models.fields.related import RelatedField
from django.db.models.sql.where import Constraint
from django.utils.tree import Node
from modeltranslation import settings
from modeltranslation.utils import build_localized_fieldname, get_language
def get_translatable_fields_for_model(model):
from modeltranslation.translator import NotRegistered, translator
try:
return translator.get_options_for_model(model).fields
except NotRegistered:
return None
def rewrite_lookup_key(model, lookup_key):
translatable_fields = get_translatable_fields_for_model(model)
if translatable_fields is not None:
pieces = lookup_key.split('__')
# If we are doing a lookup on a translatable field,
# we want to rewrite it to the actual field name
# For example, we want to rewrite "name__startswith" to "name_fr__startswith"
if pieces[0] in translatable_fields:
lookup_key = build_localized_fieldname(pieces[0], get_language())
remaining_lookup = '__'.join(pieces[1:])
if remaining_lookup:
lookup_key = '%s__%s' % (lookup_key, remaining_lookup)
pieces = lookup_key.split('__')
if len(pieces) > 1:
# Check if we are doing a lookup to a related trans model
fields_to_trans_models = get_fields_to_translatable_models(model)
for field_to_trans, transmodel in fields_to_trans_models:
if pieces[0] == field_to_trans:
sub_lookup = '__'.join(pieces[1:])
if sub_lookup:
sub_lookup = rewrite_lookup_key(transmodel, sub_lookup)
lookup_key = '%s__%s' % (pieces[0], sub_lookup)
break
return lookup_key
def rewrite_order_lookup_key(model, lookup_key):
if lookup_key.startswith('-'):
return '-' + rewrite_lookup_key(model, lookup_key[1:])
else:
return rewrite_lookup_key(model, lookup_key)
def get_fields_to_translatable_models(model):
from modeltranslation.translator import translator
results = []
for field_name in translator.get_options_for_model(model).fields.keys():
field_object, modelclass, direct, m2m = model._meta.get_field_by_name(field_name)
if direct and isinstance(field_object, RelatedField):
if get_translatable_fields_for_model(field_object.related.parent_model) is not None:
results.append((field_name, field_object.related.parent_model))
return results
class MultilingualQuerySet(models.query.QuerySet):
_rewrite = True
def __init__(self, *args, **kwargs):
super(MultilingualQuerySet, self).__init__(*args, **kwargs)
self._post_init()
def _post_init(self):
if self.model and (not self.query.order_by):
if self.model._meta.ordering:
# If we have default ordering specified on the model, set it now so that
# it can be rewritten. Otherwise sql.compiler will grab it directly from _meta
ordering = []
for key in self.model._meta.ordering:
ordering.append(rewrite_order_lookup_key(self.model, key))
self.query.add_ordering(*ordering)
# This method was not present in django-linguo
def _clone(self, *args, **kwargs):
kwargs.setdefault('_rewrite', self._rewrite)
return super(MultilingualQuerySet, self)._clone(*args, **kwargs)
# This method was not present in django-linguo
def rewrite(self, mode=True):
return self._clone(_rewrite=mode)
def _rewrite_applied_operations(self):
"""
Rewrite fields in already applied filters/ordering.
Useful when converting any QuerySet into MultilingualQuerySet.
"""
self._rewrite_where(self.query.where)
self._rewrite_where(self.query.having)
self._rewrite_order()
def _rewrite_where(self, q):
"""
Rewrite field names inside WHERE tree.
"""
if isinstance(q, tuple) and isinstance(q[0], Constraint):
c = q[0]
new_name = rewrite_lookup_key(self.model, c.field.name)
if c.field.name != new_name:
c.field = self.model._meta.get_field(new_name)
c.col = c.field.column
if isinstance(q, Node):
map(self._rewrite_where, q.children)
def _rewrite_order(self):
self.query.order_by = [rewrite_order_lookup_key(self.model, field_name)
for field_name in self.query.order_by]
# This method was not present in django-linguo
def _rewrite_q(self, q):
"""Rewrite field names inside Q call."""
if isinstance(q, tuple) and len(q) == 2:
return rewrite_lookup_key(self.model, q[0]), q[1]
if isinstance(q, Node):
q.children = map(self._rewrite_q, q.children)
return q
# This method was not present in django-linguo
def _rewrite_f(self, q):
"""
Rewrite field names inside F call.
"""
if isinstance(q, models.F):
q.name = rewrite_lookup_key(self.model, q.name)
return q
if isinstance(q, Node):
q.children = map(self._rewrite_f, q.children)
return q
def _filter_or_exclude(self, negate, *args, **kwargs):
if not self._rewrite:
return super(MultilingualQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
args = map(self._rewrite_q, args)
for key, val in kwargs.items():
new_key = rewrite_lookup_key(self.model, key)
del kwargs[key]
kwargs[new_key] = self._rewrite_f(val)
return super(MultilingualQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
def order_by(self, *field_names):
"""
Change translatable field names in an ``order_by`` argument
to translation fields for the current language.
"""
if not self._rewrite:
return super(MultilingualQuerySet, self).order_by(*field_names)
new_args = []
for key in field_names:
new_args.append(rewrite_order_lookup_key(self.model, key))
return super(MultilingualQuerySet, self).order_by(*new_args)
def update(self, **kwargs):
if not self._rewrite:
return super(MultilingualQuerySet, self).update(**kwargs)
for key, val in kwargs.items():
new_key = rewrite_lookup_key(self.model, key)
del kwargs[key]
kwargs[new_key] = self._rewrite_f(val)
return super(MultilingualQuerySet, self).update(**kwargs)
update.alters_data = True
# This method was not present in django-linguo
def create(self, **kwargs):
populate = kwargs.pop('_populate', settings.AUTO_POPULATE)
if populate:
translatable_fields = get_translatable_fields_for_model(self.model)
if translatable_fields is not None:
for key, val in kwargs.items():
if key in translatable_fields:
# Try to add value in every language
for translation_field in translatable_fields[key]:
kwargs.setdefault(translation_field.name, val)
# If not use populate feature, then normal rewriting will occur at model's __init__
# That's why it is not performed here - no reason to rewrite twice.
return super(MultilingualQuerySet, self).create(**kwargs)
class MultilingualManager(models.Manager):
use_for_related_fields = True
def rewrite(self, *args, **kwargs):
return self.get_query_set().rewrite(*args, **kwargs)
def get_query_set(self):
qs = super(MultilingualManager, self).get_query_set()
if qs.__class__ == models.query.QuerySet:
qs.__class__ = MultilingualQuerySet
else:
class NewClass(qs.__class__, MultilingualQuerySet):
pass
NewClass.__name__ = 'Multilingual%s' % qs.__class__.__name__
qs.__class__ = NewClass
qs._post_init()
qs._rewrite_applied_operations()
return qs