1
|
|
|
""" |
2
|
|
|
Custom generic managers |
3
|
|
|
""" |
4
|
|
|
import django |
5
|
|
|
from django.core.exceptions import ImproperlyConfigured |
6
|
|
|
from django.db import models |
7
|
|
|
from django.db.models.query import QuerySet |
8
|
|
|
from django.utils.translation import get_language |
9
|
|
|
from django.utils import six |
10
|
|
|
from parler import appsettings |
11
|
|
|
from parler.utils import get_active_language_choices |
12
|
|
|
|
13
|
|
|
|
14
|
|
|
class TranslatableQuerySet(QuerySet): |
15
|
|
|
""" |
16
|
|
|
An enhancement of the QuerySet which sets the objects language before they are returned. |
17
|
|
|
|
18
|
|
|
When using this class in combination with *django-polymorphic*, make sure this |
19
|
|
|
class is first in the chain of inherited classes. |
20
|
|
|
""" |
21
|
|
|
|
22
|
|
|
def __init__(self, *args, **kwargs): |
23
|
|
|
super(TranslatableQuerySet, self).__init__(*args, **kwargs) |
24
|
|
|
self._language = None |
25
|
|
|
|
26
|
|
|
def _clone(self, klass=None, setup=False, **kw): |
27
|
|
|
if django.VERSION < (1, 9): |
28
|
|
|
kw['klass'] = klass |
29
|
|
|
kw['setup'] = setup |
30
|
|
|
c = super(TranslatableQuerySet, self)._clone(**kw) |
31
|
|
|
c._language = self._language |
32
|
|
|
return c |
33
|
|
|
|
34
|
|
|
def create(self, **kwargs): |
35
|
|
|
# Pass language setting to the object, as people start assuming things |
36
|
|
|
# like .language('xx').create(..) which is a nice API after all. |
37
|
|
|
if self._language: |
38
|
|
|
kwargs['_current_language'] = self._language |
39
|
|
|
return super(TranslatableQuerySet, self).create(**kwargs) |
40
|
|
|
|
41
|
|
|
def language(self, language_code=None): |
42
|
|
|
""" |
43
|
|
|
Set the language code to assign to objects retrieved using this QuerySet. |
44
|
|
|
""" |
45
|
|
|
if language_code is None: |
46
|
|
|
language_code = appsettings.PARLER_LANGUAGES.get_default_language() |
47
|
|
|
|
48
|
|
|
self._language = language_code |
49
|
|
|
return self |
50
|
|
|
|
51
|
|
|
def translated(self, *language_codes, **translated_fields): |
52
|
|
|
""" |
53
|
|
|
Only return translated objects which of the given languages. |
54
|
|
|
|
55
|
|
|
When no language codes are given, only the currently active language is returned. |
56
|
|
|
|
57
|
|
|
.. note:: |
58
|
|
|
|
59
|
|
|
Due to Django `ORM limitations <https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships>`_, |
60
|
|
|
this method can't be combined with other filters that access the translated fields. As such, query the fields in one filter: |
61
|
|
|
|
62
|
|
|
.. code-block:: python |
63
|
|
|
|
64
|
|
|
qs.translated('en', name="Cheese Omelette") |
65
|
|
|
|
66
|
|
|
This will query the translated model for the ``name`` field. |
67
|
|
|
""" |
68
|
|
|
relname = self.model._parler_meta.root_rel_name |
69
|
|
|
|
70
|
|
|
if not language_codes: |
71
|
|
|
language_codes = (get_language(),) |
72
|
|
|
|
73
|
|
|
filters = {} |
74
|
|
|
for field_name, val in six.iteritems(translated_fields): |
75
|
|
|
if field_name.startswith('master__'): |
76
|
|
|
filters[field_name[8:]] = val # avoid translations__master__ back and forth |
77
|
|
|
else: |
78
|
|
|
filters["{0}__{1}".format(relname, field_name)] = val |
79
|
|
|
|
80
|
|
|
if len(language_codes) == 1: |
81
|
|
|
filters[relname + '__language_code'] = language_codes[0] |
82
|
|
|
return self.filter(**filters) |
83
|
|
|
else: |
84
|
|
|
filters[relname + '__language_code__in'] = language_codes |
85
|
|
|
return self.filter(**filters).distinct() |
86
|
|
|
|
87
|
|
|
def active_translations(self, language_code=None, **translated_fields): |
88
|
|
|
""" |
89
|
|
|
Only return objects which are translated, or have a fallback that should be displayed. |
90
|
|
|
|
91
|
|
|
Typically that's the currently active language and fallback language. |
92
|
|
|
This should be combined with ``.distinct()``. |
93
|
|
|
|
94
|
|
|
When ``hide_untranslated = True``, only the currently active language will be returned. |
95
|
|
|
""" |
96
|
|
|
# Default: (language, fallback) when hide_translated == False |
97
|
|
|
# Alternative: (language,) when hide_untranslated == True |
98
|
|
|
language_codes = get_active_language_choices(language_code) |
99
|
|
|
return self.translated(*language_codes, **translated_fields) |
100
|
|
|
|
101
|
|
|
def iterator(self): |
102
|
|
|
""" |
103
|
|
|
Overwritten iterator which will set the current language before returning the object. |
104
|
|
|
""" |
105
|
|
|
# Based on django-queryset-transform. |
106
|
|
|
# This object however, operates on a per-object instance |
107
|
|
|
# without breaking the result generators |
108
|
|
|
base_iterator = super(TranslatableQuerySet, self).iterator() |
109
|
|
|
for obj in base_iterator: |
110
|
|
|
# Apply the language setting to model instances only. |
111
|
|
|
if self._language and isinstance(obj, models.Model): |
112
|
|
|
obj.set_current_language(self._language) |
113
|
|
|
|
114
|
|
|
yield obj |
115
|
|
|
|
116
|
|
|
|
117
|
|
|
class TranslatableManager(models.Manager): |
118
|
|
|
""" |
119
|
|
|
The manager class which ensures the enhanced TranslatableQuerySet object is used. |
120
|
|
|
""" |
121
|
|
|
queryset_class = TranslatableQuerySet |
122
|
|
|
|
123
|
|
|
def get_queryset(self): |
124
|
|
|
if not issubclass(self.queryset_class, TranslatableQuerySet): |
125
|
|
|
raise ImproperlyConfigured("{0}.queryset_class does not inherit from TranslatableQuerySet".format(self.__class__.__name__)) |
126
|
|
|
return self.queryset_class(self.model, using=self._db) |
127
|
|
|
|
128
|
|
|
# For Django 1.5 |
129
|
|
|
# Leave for Django 1.6/1.7, so backwards compatibility can be fixed. |
130
|
|
|
# It will be removed in Django 1.8, so remove it here too to avoid false promises. |
131
|
|
|
if django.VERSION < (1, 8): |
132
|
|
|
get_query_set = get_queryset |
133
|
|
|
|
134
|
|
|
# NOTE: Fetching the queryset is done by calling self.all() here on purpose. |
135
|
|
|
# By using .all(), the proper get_query_set()/get_queryset() will be used for each Django version. |
136
|
|
|
# Django 1.4/1.5 need to use get_query_set(), because the RelatedManager overrides that. |
137
|
|
|
|
138
|
|
|
def language(self, language_code=None): |
139
|
|
|
""" |
140
|
|
|
Set the language code to assign to objects retrieved using this Manager. |
141
|
|
|
""" |
142
|
|
|
return self.all().language(language_code) |
143
|
|
|
|
144
|
|
|
def translated(self, *language_codes, **translated_fields): |
145
|
|
|
""" |
146
|
|
|
Only return objects which are translated in the given languages. |
147
|
|
|
|
148
|
|
|
NOTE: due to Django `ORM limitations <https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships>`_, |
149
|
|
|
this method can't be combined with other filters that access the translated fields. As such, query the fields in one filter: |
150
|
|
|
|
151
|
|
|
.. code-block:: python |
152
|
|
|
|
153
|
|
|
qs.translated('en', name="Cheese Omelette") |
154
|
|
|
|
155
|
|
|
This will query the translated model for the ``name`` field. |
156
|
|
|
""" |
157
|
|
|
return self.all().translated(*language_codes, **translated_fields) |
158
|
|
|
|
159
|
|
|
def active_translations(self, language_code=None, **translated_fields): |
160
|
|
|
""" |
161
|
|
|
Only return objects which are translated, or have a fallback that should be displayed. |
162
|
|
|
|
163
|
|
|
Typically that's the currently active language and fallback language. |
164
|
|
|
This should be combined with ``.distinct()``. |
165
|
|
|
|
166
|
|
|
When ``hide_untranslated = True``, only the currently active language will be returned. |
167
|
|
|
""" |
168
|
|
|
return self.all().active_translations(language_code, **translated_fields) |
169
|
|
|
|
170
|
|
|
|
171
|
|
|
# Export the names in django-hvad style too: |
172
|
|
|
TranslationQueryset = TranslatableQuerySet |
173
|
|
|
TranslationManager = TranslatableManager |
174
|
|
|
|