|
1 from django.conf import settings |
|
2 from django.contrib.contenttypes.models import ContentType |
|
3 from django.db import models |
|
4 from django.db.models import signals |
|
5 from django.db.models.base import ModelBase |
|
6 from django.utils.functional import curry |
|
7 |
|
8 from modeltranslation.fields import TranslationField |
|
9 from modeltranslation.utils import TranslationFieldDescriptor, build_localized_fieldname |
|
10 |
|
11 class AlreadyRegistered(Exception): |
|
12 pass |
|
13 |
|
14 class NotRegistered(Exception): |
|
15 pass |
|
16 |
|
17 class TranslationOptions(object): |
|
18 """ |
|
19 The TranslationOptions object is used to specify the fields to translate. |
|
20 |
|
21 The options are registered in combination with a model class at the |
|
22 ``modeltranslation.translator.translator`` instance. |
|
23 |
|
24 It caches the content type of the translated model for faster lookup later |
|
25 on. |
|
26 """ |
|
27 def __init__(self, *args, **kwargs): |
|
28 # self.translation_model = None |
|
29 self.model_ct = None |
|
30 self.localized_fieldnames = list() |
|
31 |
|
32 #def get_localized_fieldnames(model): |
|
33 |
|
34 def add_localized_fields(model): |
|
35 """ |
|
36 Monkey patchs the original model class to provide additional fields for |
|
37 every language. Only do that for fields which are defined in the |
|
38 translation options of the model. |
|
39 |
|
40 Returns a dict mapping the original fieldname to a list containing the names |
|
41 of the localized fields created for the original field. |
|
42 """ |
|
43 localized_fields = dict() |
|
44 translation_opts = translator.get_options_for_model(model) |
|
45 for field_name in translation_opts.fields: |
|
46 localized_fields[field_name] = list() |
|
47 for l in settings.LANGUAGES: |
|
48 # Construct the name for the localized field |
|
49 localized_field_name = build_localized_fieldname(field_name, l[0]) |
|
50 # Check if the model already has a field by that name |
|
51 if hasattr(model, localized_field_name): |
|
52 raise ValueError("Error adding translation field. The model "\ |
|
53 "'%s' already contains a field named '%s'. "\ |
|
54 % (instance.__class__.__name__, localized_field_name)) |
|
55 |
|
56 # This approach implements the translation fields as full valid |
|
57 # django model fields and therefore adds them via add_to_class |
|
58 localized_field = model.add_to_class(localized_field_name, |
|
59 TranslationField(model._meta.get_field(field_name), |
|
60 l[0])) |
|
61 localized_fields[field_name].append(localized_field_name) |
|
62 |
|
63 |
|
64 return localized_fields |
|
65 # model.add_to_class('current_language', CurrentLanguageField()) |
|
66 |
|
67 #def translated_model_initialized(field_names, instance, **kwargs): |
|
68 #print "translated_model_initialized instance:", instance, ", field:", field_names |
|
69 #for field_name in field_names: |
|
70 #initial_val = getattr(instance, field_name) |
|
71 #print " field: %s, initialval: %s" % (field_name, initial_val) |
|
72 #setattr(instance.__class__, field_name, TranslationFieldDescriptor(field_name, |
|
73 #initial_val)) |
|
74 #def translated_model_initializing(sender, args, kwargs, **signal_kwargs): |
|
75 #print "translated_model_initializing", sender, args, kwargs |
|
76 #trans_opts = translator.get_options_for_model(sender) |
|
77 #for field_name in trans_opts.fields: |
|
78 #setattr(sender, field_name, TranslationFieldDescriptor(field_name)) |
|
79 |
|
80 |
|
81 class Translator(object): |
|
82 """ |
|
83 A Translator object encapsulates an instance of a translator. Models are |
|
84 registered with the Translator using the register() method. |
|
85 """ |
|
86 def __init__(self): |
|
87 self._registry = {} # model_class class -> translation_opts instance |
|
88 |
|
89 def register(self, model_or_iterable, translation_opts, **options): |
|
90 """ |
|
91 Registers the given model(s) with the given translation options. |
|
92 |
|
93 The model(s) should be Model classes, not instances. |
|
94 |
|
95 If a model is already registered for translation, this will raise |
|
96 AlreadyRegistered. |
|
97 """ |
|
98 # Don't import the humongous validation code unless required |
|
99 if translation_opts and settings.DEBUG: |
|
100 from django.contrib.admin.validation import validate |
|
101 else: |
|
102 validate = lambda model, adminclass: None |
|
103 |
|
104 #if not translation_opts: |
|
105 #translation_opts = TranslationOptions |
|
106 if isinstance(model_or_iterable, ModelBase): |
|
107 model_or_iterable = [model_or_iterable] |
|
108 |
|
109 for model in model_or_iterable: |
|
110 if model in self._registry: |
|
111 raise AlreadyRegistered('The model %s is already registered for translation' % model.__name__) |
|
112 |
|
113 # If we got **options then dynamically construct a subclass of |
|
114 # translation_opts with those **options. |
|
115 if options: |
|
116 # For reasons I don't quite understand, without a __module__ |
|
117 # the created class appears to "live" in the wrong place, |
|
118 # which causes issues later on. |
|
119 options['__module__'] = __name__ |
|
120 translation_opts = type("%sAdmin" % model.__name__, (translation_opts,), options) |
|
121 |
|
122 # Validate (which might be a no-op) |
|
123 #validate(translation_opts, model) |
|
124 |
|
125 # Store the translation class associated to the model |
|
126 self._registry[model] = translation_opts |
|
127 |
|
128 # Get the content type of the original model and store it on the |
|
129 # translation options for faster lookup later on. |
|
130 translation_opts.model_ct = ContentType.objects.get_for_model(model) |
|
131 |
|
132 # Add the localized fields to the model and store the names of these |
|
133 # fields in the model's translation options for faster lookup later |
|
134 # on. |
|
135 translation_opts.localized_fieldnames = add_localized_fields(model) |
|
136 |
|
137 # Create a reverse dict mapping the localized_fieldnames to the |
|
138 # original fieldname |
|
139 rev_dict = dict() |
|
140 for orig_name, loc_names in translation_opts.localized_fieldnames.items(): |
|
141 for ln in loc_names: |
|
142 rev_dict[ln] = orig_name |
|
143 |
|
144 translation_opts.localized_fieldnames_rev = rev_dict |
|
145 |
|
146 # print "Applying descriptor field for model %s" % model |
|
147 for field_name in translation_opts.fields: |
|
148 setattr(model, field_name, TranslationFieldDescriptor(field_name)) |
|
149 |
|
150 #signals.pre_init.connect(translated_model_initializing, sender=model, weak=False) |
|
151 |
|
152 def unregister(self, model_or_iterable): |
|
153 """ |
|
154 Unregisters the given model(s). |
|
155 |
|
156 If a model isn't already registered, this will raise NotRegistered. |
|
157 """ |
|
158 if isinstance(model_or_iterable, ModelBase): |
|
159 model_or_iterable = [model_or_iterable] |
|
160 for model in model_or_iterable: |
|
161 if model not in self._registry: |
|
162 raise NotRegistered('The model "%s" is not registered for translation' % model.__name__) |
|
163 del self._registry[model] |
|
164 |
|
165 def get_options_for_model(self, model): |
|
166 """ |
|
167 Returns the translation options for the given ``model``. If the |
|
168 ``model`` is not registered a ``NotRegistered`` exception is raised. |
|
169 """ |
|
170 try: |
|
171 return self._registry[model] |
|
172 except KeyError: |
|
173 raise NotRegistered('The model "%s" is not registered for translation' % model.__name__) |
|
174 |
|
175 |
|
176 # This global object represents the singleton translator object |
|
177 translator = Translator() |