web/lib/modeltranslation/translator.py
author ymh <ymh.work@gmail.com>
Fri, 22 Jan 2010 04:15:42 +0100
changeset 9 60555e0d17f4
parent 5 10b1f6d8a5d2
permissions -rw-r--r--
commit patch from issue : http://code.google.com/p/django-modeltranslation/issues/detail?id=1

from django.conf import settings
#from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import signals
from django.db.models.base import ModelBase
from django.utils.functional import curry

from modeltranslation.fields import TranslationField 
from modeltranslation.utils import TranslationFieldDescriptor, build_localized_fieldname

class AlreadyRegistered(Exception):
    pass

class NotRegistered(Exception):
    pass

class TranslationOptions(object):
    """
    The TranslationOptions object is used to specify the fields to translate.
    
    The options are registered in combination with a model class at the
    ``modeltranslation.translator.translator`` instance.
    
    It caches the content type of the translated model for faster lookup later
    on.
    """
    def __init__(self, *args, **kwargs):
        # self.translation_model = None
        #self.model_ct = None
        self.localized_fieldnames = list()
        
#def get_localized_fieldnames(model):
        
def add_localized_fields(model):
    """
    Monkey patchs the original model class to provide additional fields for 
    every language. Only do that for fields which are defined in the 
    translation options of the model.
    
    Returns a dict mapping the original fieldname to a list containing the names 
    of the localized fields created for the original field.
    """
    localized_fields = dict()
    translation_opts = translator.get_options_for_model(model)
    for field_name in translation_opts.fields:                
        localized_fields[field_name] = list()
        for l in settings.LANGUAGES:            
            # Construct the name for the localized field
            localized_field_name = build_localized_fieldname(field_name, l[0])
            # Check if the model already has a field by that name 
            if hasattr(model, localized_field_name):
                raise ValueError("Error adding translation field. The model "\
                                 "'%s' already contains a field named '%s'. "\
                                 % (instance.__class__.__name__, localized_field_name))
            
            # This approach implements the translation fields as full valid
            # django model fields and therefore adds them via add_to_class
            localized_field = model.add_to_class(localized_field_name,
                                                 TranslationField(model._meta.get_field(field_name), 
                                                                  l[0]))             
            localized_fields[field_name].append(localized_field_name)
            
        
    return localized_fields        
    # model.add_to_class('current_language', CurrentLanguageField())        
    
#def translated_model_initialized(field_names, instance, **kwargs):
    #print "translated_model_initialized instance:", instance, ", field:", field_names
    #for field_name in field_names:
        #initial_val = getattr(instance, field_name)
        #print "  field: %s, initialval: %s" % (field_name, initial_val)
        #setattr(instance.__class__, field_name, TranslationFieldDescriptor(field_name,
                                                                           #initial_val))
#def translated_model_initializing(sender, args, kwargs, **signal_kwargs):
    #print "translated_model_initializing", sender, args, kwargs    
    #trans_opts = translator.get_options_for_model(sender)
    #for field_name in trans_opts.fields:
        #setattr(sender, field_name, TranslationFieldDescriptor(field_name))
                                                           
        
class Translator(object):
    """
    A Translator object encapsulates an instance of a translator. Models are
    registered with the Translator using the register() method.
    """
    def __init__(self):
        self._registry = {} # model_class class -> translation_opts instance

    def register(self, model_or_iterable, translation_opts, **options):
        """
        Registers the given model(s) with the given translation options.

        The model(s) should be Model classes, not instances.

        If a model is already registered for translation, this will raise 
        AlreadyRegistered.
        """
        # Don't import the humongous validation code unless required
        if translation_opts and settings.DEBUG:
            from django.contrib.admin.validation import validate
        else:
            validate = lambda model, adminclass: None

        #if not translation_opts:
            #translation_opts = TranslationOptions                    
        if isinstance(model_or_iterable, ModelBase):
            model_or_iterable = [model_or_iterable]
        
        for model in model_or_iterable:
            if model in self._registry:
                raise AlreadyRegistered('The model %s is already registered for translation' % model.__name__)

            # If we got **options then dynamically construct a subclass of
            # translation_opts with those **options.
            if options:
                # For reasons I don't quite understand, without a __module__
                # the created class appears to "live" in the wrong place,
                # which causes issues later on.
                options['__module__'] = __name__
                translation_opts = type("%sAdmin" % model.__name__, (translation_opts,), options)

            # Validate (which might be a no-op)
            #validate(translation_opts, model)

            # Store the translation class associated to the model
            self._registry[model] = translation_opts
                    
            # Get the content type of the original model and store it on the
            # translation options for faster lookup later on.
            #translation_opts.model_ct = ContentType.objects.get_for_model(model)                    
                                       
            # Add the localized fields to the model and store the names of these
            # fields in the model's translation options for faster lookup later
            # on.                                       
            translation_opts.localized_fieldnames = add_localized_fields(model)
            
            # Create a reverse dict mapping the localized_fieldnames to the 
            # original fieldname
            rev_dict = dict()
            for orig_name, loc_names in translation_opts.localized_fieldnames.items():
                for ln in loc_names:
                    rev_dict[ln] = orig_name
                    
            translation_opts.localized_fieldnames_rev = rev_dict                    
                        
        # print "Applying descriptor field for model %s" % model                        
        for field_name in translation_opts.fields:
            setattr(model, field_name, TranslationFieldDescriptor(field_name))
                        
        #signals.pre_init.connect(translated_model_initializing, sender=model, weak=False)                        
            
    def unregister(self, model_or_iterable):
        """
        Unregisters the given model(s).

        If a model isn't already registered, this will raise NotRegistered.
        """
        if isinstance(model_or_iterable, ModelBase):
            model_or_iterable = [model_or_iterable]
        for model in model_or_iterable:
            if model not in self._registry:
                raise NotRegistered('The model "%s" is not registered for translation' % model.__name__)
            del self._registry[model]
            
    def get_options_for_model(self, model):
        """
        Returns the translation options for the given ``model``. If the 
        ``model`` is not registered a ``NotRegistered`` exception is raised.
        """
        try:
            return self._registry[model]
        except KeyError:
            raise NotRegistered('The model "%s" is not registered for translation' % model.__name__)
                

# This global object represents the singleton translator object
translator = Translator()