mirror of
https://github.com/Hopiu/django-modeltranslation.git
synced 2026-03-16 22:10:31 +00:00
Add fallback to values and values_list (close #258).
This commit is contained in:
parent
42c949ddb5
commit
19c2a90e9f
3 changed files with 123 additions and 16 deletions
|
|
@ -123,7 +123,7 @@ These manager methods perform rewriting:
|
|||
- ``order_by()``
|
||||
- ``update()``
|
||||
- ``only()``, ``defer()``
|
||||
- ``values()``, ``values_list()``
|
||||
- ``values()``, ``values_list()``, with :ref:`fallback <fallback>` mechanism
|
||||
- ``dates()``
|
||||
- ``select_related()``
|
||||
- ``create()``, with optional auto-population_ feature
|
||||
|
|
@ -214,18 +214,25 @@ Falling back
|
|||
------------
|
||||
|
||||
Modeltranslation provides a mechanism to control behaviour of data access in case of empty
|
||||
translation values. This mechanism affects field access.
|
||||
translation values. This mechanism affects field access, as well as ``values()``
|
||||
and ``values_list()`` manager methods.
|
||||
|
||||
Consider the ``News`` example: a creator of some news hasn't specified its German title and
|
||||
content, but only English ones. Then if a German visitor is viewing the site, we would rather show
|
||||
him English title/content of the news than display empty strings. This is called *fallback*. ::
|
||||
|
||||
News.title_en = 'English title'
|
||||
News.title_de = ''
|
||||
print News.title
|
||||
news.title_en = 'English title'
|
||||
news.title_de = ''
|
||||
print news.title
|
||||
# If current active language is German, it should display the title_de field value ('').
|
||||
# But if fallback is enabled, it would display 'English title' instead.
|
||||
|
||||
# Similarly for manager
|
||||
news.save()
|
||||
print News.objects.filter(pk=news.pk).values_list('title', flat=True)[0]
|
||||
# As above: if current active language is German and fallback to English is enabled,
|
||||
# it would display 'English title'.
|
||||
|
||||
There are several ways of controlling fallback, described below.
|
||||
|
||||
.. _fallback_lang:
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ except ImportError:
|
|||
from modeltranslation import settings
|
||||
from modeltranslation.fields import TranslationField
|
||||
from modeltranslation.utils import (build_localized_fieldname, get_language,
|
||||
auto_populate)
|
||||
auto_populate, resolution_order)
|
||||
|
||||
|
||||
def get_translatable_fields_for_model(model):
|
||||
|
|
@ -56,6 +56,24 @@ def rewrite_lookup_key(model, lookup_key):
|
|||
return '__'.join(pieces)
|
||||
|
||||
|
||||
def append_fallback(model, fields):
|
||||
"""
|
||||
If translated field is encountered, add also all its fallback fields.
|
||||
Returns tuple: (set_of_new_fields_to_use, set_of_translated_field_names)
|
||||
"""
|
||||
fields = set(fields)
|
||||
trans = set()
|
||||
from modeltranslation.translator import translator
|
||||
opts = translator.get_options_for_model(model)
|
||||
for key, _ in opts.fields.items():
|
||||
if key in fields:
|
||||
langs = resolution_order(get_language(), getattr(model, key).fallback_languages)
|
||||
fields = fields.union(build_localized_fieldname(key, lang) for lang in langs)
|
||||
fields.remove(key)
|
||||
trans.add(key)
|
||||
return fields, trans
|
||||
|
||||
|
||||
def append_translated(model, fields):
|
||||
"If translated field is encountered, add also all its translation fields."
|
||||
fields = set(fields)
|
||||
|
|
@ -343,24 +361,22 @@ class MultilingualQuerySet(models.query.QuerySet):
|
|||
if not fields:
|
||||
# Emulate original queryset behaviour: get all fields that are not translation fields
|
||||
fields = self._get_original_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
|
||||
return self._clone(klass=FallbackValuesQuerySet, setup=True, _fields=fields)
|
||||
|
||||
# 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)
|
||||
flat = kwargs.pop('flat', False)
|
||||
if kwargs:
|
||||
raise TypeError('Unexpected keyword arguments to values_list: %s' % (list(kwargs),))
|
||||
if flat and len(fields) > 1:
|
||||
raise TypeError("'flat' is not valid when values_list is "
|
||||
"called with more than one field.")
|
||||
if not fields:
|
||||
# Emulate original queryset behaviour: get all fields that are not translation fields
|
||||
fields = self._get_original_fields()
|
||||
new_args = []
|
||||
for key in fields:
|
||||
new_args.append(rewrite_lookup_key(self.model, key))
|
||||
return super(MultilingualQuerySet, self).values_list(*new_args, **kwargs)
|
||||
return self._clone(klass=FallbackValuesListQuerySet, setup=True, flat=flat, _fields=fields)
|
||||
|
||||
# This method was not present in django-linguo
|
||||
def dates(self, field_name, *args, **kwargs):
|
||||
|
|
@ -370,6 +386,58 @@ class MultilingualQuerySet(models.query.QuerySet):
|
|||
return super(MultilingualQuerySet, self).dates(new_key, *args, **kwargs)
|
||||
|
||||
|
||||
class FallbackValuesQuerySet(models.query.ValuesQuerySet, MultilingualQuerySet):
|
||||
def _setup_query(self):
|
||||
original = self._fields
|
||||
new_fields, self.translation_fields = append_fallback(self.model, original)
|
||||
self._fields = list(new_fields)
|
||||
self.fields_to_del = new_fields - set(original)
|
||||
super(FallbackValuesQuerySet, self)._setup_query()
|
||||
|
||||
class X(object):
|
||||
# This stupid class is needed as object use __slots__ and has no __dict__.
|
||||
pass
|
||||
|
||||
def iterator(self):
|
||||
instance = self.X()
|
||||
for row in super(FallbackValuesQuerySet, self).iterator():
|
||||
instance.__dict__.update(row)
|
||||
for key in self.translation_fields:
|
||||
row[key] = getattr(self.model, key).__get__(instance, None)
|
||||
for key in self.fields_to_del:
|
||||
del row[key]
|
||||
yield row
|
||||
|
||||
def _clone(self, klass=None, setup=False, **kwargs):
|
||||
c = super(FallbackValuesQuerySet, self)._clone(klass, **kwargs)
|
||||
c.fields_to_del = self.fields_to_del
|
||||
c.translation_fields = self.translation_fields
|
||||
if setup and hasattr(c, '_setup_query'):
|
||||
c._setup_query()
|
||||
return c
|
||||
|
||||
|
||||
class FallbackValuesListQuerySet(FallbackValuesQuerySet):
|
||||
def iterator(self):
|
||||
for row in super(FallbackValuesListQuerySet, self).iterator():
|
||||
if self.flat and len(self.original_fields) == 1:
|
||||
yield row[self.original_fields[0]]
|
||||
else:
|
||||
yield tuple(row[f] for f in self.original_fields)
|
||||
|
||||
def _setup_query(self):
|
||||
self.original_fields = self._fields
|
||||
super(FallbackValuesListQuerySet, self)._setup_query()
|
||||
|
||||
def _clone(self, *args, **kwargs):
|
||||
clone = super(FallbackValuesListQuerySet, self)._clone(*args, **kwargs)
|
||||
clone.original_fields = self.original_fields
|
||||
if not hasattr(clone, "flat"):
|
||||
# Only assign flat if the clone didn't already get it from kwargs
|
||||
clone.flat = self.flat
|
||||
return clone
|
||||
|
||||
|
||||
def get_queryset(obj):
|
||||
if hasattr(obj, 'get_queryset'):
|
||||
return obj.get_queryset()
|
||||
|
|
|
|||
|
|
@ -2449,6 +2449,38 @@ class TestManager(ModeltranslationTestBase):
|
|||
self.assertEqual(titles_for_en, ('most', 'more_en', 'more_de', 'least'))
|
||||
self.assertEqual(titles_for_de, ('most', 'more_de', 'more_en', 'least'))
|
||||
|
||||
def assert_fallback(self, method, expected1, *args, **kwargs):
|
||||
transform = kwargs.pop('transform', lambda x: x)
|
||||
expected2 = kwargs.pop('expected_de', expected1)
|
||||
with default_fallback():
|
||||
# Fallback is ('de',)
|
||||
obj = method(*args, **kwargs)[0]
|
||||
with override('de'):
|
||||
obj2 = method(*args, **kwargs)[0]
|
||||
self.assertEqual(transform(obj), expected1)
|
||||
self.assertEqual(transform(obj2), expected2)
|
||||
|
||||
def test_values_fallback(self):
|
||||
manager = models.ManagerTestModel.objects
|
||||
manager.create(title_en='', title_de='de')
|
||||
self.assertEqual('en', get_language())
|
||||
|
||||
self.assert_fallback(manager.values, 'de', 'title', transform=lambda x: x['title'])
|
||||
self.assert_fallback(manager.values_list, 'de', 'title', flat=True)
|
||||
self.assert_fallback(manager.values_list, ('de', '', 'de'), 'title', 'title_en', 'title_de')
|
||||
|
||||
# Settings are taken into account - fallback can be disabled
|
||||
with override_settings(MODELTRANSLATION_ENABLE_FALLBACKS=False):
|
||||
self.assert_fallback(manager.values, '', 'title', expected_de='de',
|
||||
transform=lambda x: x['title'])
|
||||
|
||||
# Test fallback values
|
||||
manager = models.FallbackModel.objects
|
||||
manager.create()
|
||||
|
||||
self.assert_fallback(manager.values, 'fallback', 'title', transform=lambda x: x['title'])
|
||||
self.assert_fallback(manager.values_list, ('fallback', 'fallback'), 'title', 'text')
|
||||
|
||||
def test_values(self):
|
||||
manager = models.ManagerTestModel.objects
|
||||
id1 = manager.create(title_en='en', title_de='de').pk
|
||||
|
|
|
|||
Loading…
Reference in a new issue