web/lib/modeltranslation/translator.py
changeset 5 10b1f6d8a5d2
child 9 60555e0d17f4
equal deleted inserted replaced
4:b77683731f25 5:10b1f6d8a5d2
       
     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()