django-modeltranslation/modeltranslation/manager.py
2014-02-25 22:06:55 +01:00

317 lines
12 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 import FieldDoesNotExist
from django.db.models.fields.related import RelatedField, RelatedObject
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,
auto_populate)
def get_translatable_fields_for_model(model):
from modeltranslation.translator import NotRegistered, translator
try:
return translator.get_options_for_model(model).get_field_names()
except NotRegistered:
return None
def rewrite_lookup_key(model, lookup_key):
pieces = lookup_key.split('__', 1)
original_key = pieces[0]
translatable_fields = get_translatable_fields_for_model(model)
if translatable_fields is not None:
# 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:
pieces[0] = build_localized_fieldname(pieces[0], get_language())
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:
# Check ``original key``, as pieces[0] may have been already rewritten.
if original_key == field_to_trans:
pieces[1] = rewrite_lookup_key(transmodel, pieces[1])
break
return '__'.join(pieces)
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)
_F2TM_CACHE = {}
def get_fields_to_translatable_models(model):
if model not in _F2TM_CACHE:
results = []
for field_name in model._meta.get_all_field_names():
field_object, modelclass, direct, m2m = model._meta.get_field_by_name(field_name)
# Direct relationship
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))
# Reverse relationship
if isinstance(field_object, RelatedObject):
if get_translatable_fields_for_model(field_object.model) is not None:
results.append((field_name, field_object.model))
_F2TM_CACHE[model] = results
return _F2TM_CACHE[model]
_C2F_CACHE = {}
def get_field_by_colum_name(model, col):
# First, try field with the column name
try:
field = model._meta.get_field(col)
if field.column == col:
return field
except FieldDoesNotExist:
pass
field = _C2F_CACHE.get((model, col), None)
if field:
return field
# D'oh, need to search through all of them.
for field in model._meta.fields:
if field.column == col:
_C2F_CACHE[(model, col)] = field
return field
assert False, "No field found for column %s" % col
class MultilingualQuerySet(models.query.QuerySet):
def __init__(self, *args, **kwargs):
super(MultilingualQuerySet, self).__init__(*args, **kwargs)
self._post_init()
def _post_init(self):
self._rewrite = True
self._populate = None
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, klass=None, *args, **kwargs):
if klass is not None and not issubclass(klass, MultilingualQuerySet):
class NewClass(klass, MultilingualQuerySet):
pass
NewClass.__name__ = 'Multilingual%s' % klass.__name__
klass = NewClass
kwargs.setdefault('_rewrite', self._rewrite)
kwargs.setdefault('_populate', self._populate)
return super(MultilingualQuerySet, self)._clone(klass, *args, **kwargs)
# This method was not present in django-linguo
def rewrite(self, mode=True):
return self._clone(_rewrite=mode)
# This method was not present in django-linguo
def populate(self, mode='all'):
"""
Overrides the translation fields population mode for this query set.
"""
return self._clone(_populate=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]
if c.field is None:
c.field = get_field_by_colum_name(self.model, c.col)
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):
for child in q.children:
self._rewrite_where(child)
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 = list(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 = list(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
@property
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 self._populate
# This method was not present in django-linguo
def create(self, **kwargs):
"""
Allows to override population mode with a ``populate`` method.
"""
with auto_populate(self._populate_mode):
return super(MultilingualQuerySet, self).create(**kwargs)
# This method was not present in django-linguo
def get_or_create(self, **kwargs):
"""
Allows to override population mode with a ``populate`` method.
"""
with auto_populate(self._populate_mode):
return super(MultilingualQuerySet, self).get_or_create(**kwargs)
def _append_translated(self, fields):
"If translated field is encountered, add also all its translation fields."
fields = set(fields)
from modeltranslation.translator import translator
opts = translator.get_options_for_model(self.model)
for key, translated in opts.fields.items():
if key in fields:
fields = fields.union(f.name for f in translated)
return fields
# This method was not present in django-linguo
def defer(self, *fields):
fields = self._append_translated(fields)
return super(MultilingualQuerySet, self).defer(*fields)
# This method was not present in django-linguo
def only(self, *fields):
fields = self._append_translated(fields)
return super(MultilingualQuerySet, self).only(*fields)
# This method was not present in django-linguo
def raw_values(self, *fields):
return super(MultilingualQuerySet, self).values(*fields)
# This method was not present in django-linguo
def values(self, *fields):
if not self._rewrite:
return super(MultilingualQuerySet, self).values(*fields)
new_args = []
for key in fields:
new_args.append(rewrite_lookup_key(self.model, key))
vqs = super(MultilingualQuerySet, self).values(*new_args)
vqs.field_names = list(fields)
return vqs
# This method was not present in django-linguo
def values_list(self, *fields, **kwargs):
if not self._rewrite:
return super(MultilingualQuerySet, self).values_list(*fields, **kwargs)
new_args = []
for key in fields:
new_args.append(rewrite_lookup_key(self.model, key))
return super(MultilingualQuerySet, self).values_list(*new_args, **kwargs)
# This method was not present in django-linguo
def dates(self, field_name, *args, **kwargs):
if not self._rewrite:
return super(MultilingualQuerySet, self).dates(field_name, *args, **kwargs)
new_key = rewrite_lookup_key(self.model, field_name)
return super(MultilingualQuerySet, self).dates(new_key, *args, **kwargs)
class MultilingualManager(models.Manager):
use_for_related_fields = True
def rewrite(self, *args, **kwargs):
return self.get_query_set().rewrite(*args, **kwargs)
def populate(self, *args, **kwargs):
return self.get_query_set().populate(*args, **kwargs)
def raw_values(self, *args, **kwargs):
return self.get_query_set().raw_values(*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