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
9 from modeltranslation.fields import TranslationField
10 from modeltranslation.utils import (TranslationFieldDescriptor,
11 build_localized_fieldname)
14 class AlreadyRegistered(Exception):
18 class NotRegistered(Exception):
22 class TranslationOptions(object):
24 The TranslationOptions object is used to specify the fields to translate.
26 The options are registered in combination with a model class at the
27 ``modeltranslation.translator.translator`` instance.
29 It caches the content type of the translated model for faster lookup later
32 def __init__(self, *args, **kwargs):
33 # self.translation_model = None
35 self.localized_fieldnames = list()
38 def add_localized_fields(model):
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.
44 Returns a dict mapping the original fieldname to a list containing the
45 names of the localized fields created for the original field.
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))
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( \
65 TranslationField(model._meta.get_field(field_name), l[0]))
66 localized_fields[field_name].append(localized_field_name)
67 return localized_fields
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))
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))
87 class Translator(object):
89 A Translator object encapsulates an instance of a translator. Models are
90 registered with the Translator using the register() method.
93 # model_class class -> translation_opts instance
96 def register(self, model_or_iterable, translation_opts, **options):
98 Registers the given model(s) with the given translation options.
100 The model(s) should be Model classes, not instances.
102 If a model is already registered for translation, this will raise
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
109 validate = lambda model, adminclass: None
111 #if not translation_opts:
112 #translation_opts = TranslationOptions
113 if isinstance(model_or_iterable, ModelBase):
114 model_or_iterable = [model_or_iterable]
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__)
121 # If we got **options then dynamically construct a subclass of
122 # translation_opts with those **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)
131 # Validate (which might be a no-op)
132 #validate(translation_opts, model)
134 # Store the translation class associated to the model
135 self._registry[model] = translation_opts
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)
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
145 translation_opts.localized_fieldnames = add_localized_fields(model)
147 # Create a reverse dict mapping the localized_fieldnames to the
150 for orig_name, loc_names in \
151 translation_opts.localized_fieldnames.items():
153 rev_dict[ln] = orig_name
155 translation_opts.localized_fieldnames_rev = rev_dict
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))
161 #signals.pre_init.connect(translated_model_initializing, sender=model,
164 def unregister(self, model_or_iterable):
166 Unregisters the given model(s).
168 If a model isn't already registered, this will raise NotRegistered.
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]
178 def get_options_for_model(self, model):
180 Returns the translation options for the given ``model``. If the
181 ``model`` is not registered a ``NotRegistered`` exception is raised.
184 return self._registry[model]
186 raise NotRegistered('The model "%s" is not registered for '
187 'translation' % model.__name__)
190 # This global object represents the singleton translator object
191 translator = Translator()