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