def get_localized_property(context, field=None, language=None): ''' When accessing to the name of the field itself, the value in the current language will be returned. Unless it's set, the value in the default language will be returned. ''' if language: return getattr(context, get_real_fieldname(field, language)) if hasattr(settings, 'FALLBACK_LANGUAGES'): attrs = [translation.get_language()] attrs += get_fallback_languages() else: attrs = [ translation.get_language(), translation.get_language()[:2], settings.LANGUAGE_CODE, ] # print(context.__dict__) # print(getattr(context, 'title_ru', None)) def predicate(x): value = getattr(context, get_real_fieldname(field, x), None) return value if valid_for_gettext(value) else None return first_match(predicate, attrs)
def _get_localized_field_checks(self): """ Get the checks we must perform for the localized fields. """ localized_fields_checks = [] for localized_field in self.instance.localized_fields: if self.cleaned_data.get(localized_field) is None: continue f = getattr(self.instance.__class__, localized_field, None) if f and f.unique: if f.unique: local_name = get_real_fieldname(localized_field, self.language) localized_fields_checks.append( (localized_field, local_name)) return localized_fields_checks
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=ErrorList, label_suffix=':', empty_permitted=False, instance=None, js=None): # store language so it can be used later self.language = get_language() locale_data = initial or SortedDict() # Set up the initial data for the form for localized_field in self.localized_fields: # determine localized name of the field, because it is called like # that in the initial dict local_name = get_real_fieldname(localized_field, self.language) if initial: # get value from initial if it is defined initial_value = initial.get(local_name) if not initial_value and instance: # try model if defined initial_value = getattr(instance, localized_field) locale_data[localized_field] = initial_value elif instance is not None: locale_data[localized_field] = getattr(instance, localized_field) super(LocalisedForm, self).__init__(data, files, auto_id, prefix, locale_data, error_class, label_suffix, empty_permitted, instance)
def localize_fields(cls, localized_fields): """ For each field name in localized_fields, for each language in settings.LANGUAGES, add fields to cls, and remove the original field, instead replace it with a DefaultFieldDescriptor, which always returns the field in the current language. """ # never do this twice if hasattr(cls, 'localized_fields'): return cls # MSGID_LANGUAGE is the language that is used for the gettext message id's. # If it is not available, because the site isn't using subsites, the # LANGUAGE_CODE is good too. MSGID_LANGUAGE gives the opportunity to # specify a language not available in the site but which is still used for # the message id's. msgid_language = getattr(settings, 'MSGID_LANGUAGE', settings.LANGUAGE_CODE) # set the localized fields property cls.localized_fields = localized_fields for field in localized_fields: original_attr = get_field_from_model_by_name(cls, field) for cnt, language_code in enumerate(get_all_language_codes()): i18n_attr = copy.copy(original_attr) # add support for south introspection. i18n_attr._south_introspects = True i18n_attr.original_fieldname = field i18n_attr.include_in_xml = False lang_attr_name = get_real_fieldname(field, language_code) i18n_attr.name = lang_attr_name i18n_attr.creation_counter = i18n_attr.creation_counter + .01 * cnt # null must be allowed for the message id language because this # language might not be available at all in the backend if not i18n_attr.null and i18n_attr.default is NOT_PROVIDED: i18n_attr.null = True if language_code != msgid_language: # no validation for the fields that are language specific if not i18n_attr.blank: i18n_attr.blank = True if i18n_attr.verbose_name: i18n_attr.verbose_name = format_lazy('{0}{1}', i18n_attr.verbose_name, u' (%s)' % language_code) cls.add_to_class(lang_attr_name, i18n_attr) # delete original field del cls._meta.local_fields[cls._meta.local_fields.index(original_attr)] # copy some values and functions from the original_attr # so the field can emulate the original_attr as good as possible kwargs = { 'serialize': getattr(original_attr, 'serialize', True), 'extra_attrs': getattr(original_attr, 'extra_attrs', None), 'max_length': getattr(original_attr, 'max_length', None), 'min_length': getattr(original_attr, 'min_length', None), 'form_field': original_attr.formfield(**FORMFIELD_FOR_DBFIELD_DEFAULTS.get( original_attr.__class__, {})), 'get_internal_type': original_attr.get_internal_type, 'unique': getattr(original_attr, 'unique', False), 'to_python': original_attr.to_python, } # copy __serialize__ if it was defined on the original attr if hasattr(original_attr, '__serialize__'): kwargs['__serialize__'] = original_attr.__serialize__ # add the DefaultFieldDescriptor where the original_attr was. cls.add_to_class(field, DefaultFieldDescriptor(field, **kwargs)) # update fields cache try: cls._meta._fill_fields_cache() except AttributeError: # Django 1.8 removed _fill_fields_cache cls._meta._expire_cache() cls._meta._get_fields(reverse=False) # return the finished product return cls
def __get__(self, obj, typ=None): """ Read the localised version of the field this descriptor emulates. First try to see if the localised field is really set. If not, then use ugettext_lazy to find a tranlation in the current language for this field. """ # self must be returned in a getattr context. if obj is None: return self current_language = translation.get_language() real_field_name = get_real_fieldname(self.name, current_language) vo = GettextVO() # first check if the database contains the localized data vo.stored_value = getattr(obj, real_field_name) if getattr(settings, 'I18N_NOFALLBACK', False): return vo.stored_value # the database does not have our localized data. # check if we have a translation, first get the msgid, as a unicode string. vo.msgid = get_localized_property( obj, self.name, getattr(settings, 'MSGID_LANGUAGE', settings.LANGUAGE_CODE)) # check the translation in the current language # but avoid empty string and None if valid_for_gettext(vo.msgid): vo.msg = self.to_python(translation.ugettext(force_text(vo.msgid))) elif valid_for_gettext(vo.stored_value): # we can not use the msgid for gettext but we did find a valid # translation in the database. Fine we stop here and return that # value. No need for a standin, because we don't have a catalog value. return vo.stored_value else: # we can not use the msgid for gettext lookups, so there is no # point in trying. Check for fallback languages in database. vo.fallback = get_localized_property(obj, self.name) if not valid_for_gettext(vo.fallback): # Also if we are sure we don't have any old # translations in the catalog or something for the fallback # languages in the database, we do not need to return a standin either return vo.msgid # we got here so we've got a valid messageid. Now collect data from the catalog(s) # if there isn't anything new in the catalog belonging to the current language: if vo.msg == vo.msgid: # maybe we have a translation in any of the fallback languages. if hasattr(settings, 'FALLBACK_LANGUAGES'): # first check if the database has the localized data in # any of the fallback languages. vo.fallback = get_localized_property(obj, self.name) # if the fallback is the same as the msgid, go and look in the catalog if vo.fallback == vo.msgid: # there might be a translation in any # of the fallback languages. for fallback in get_fallback_languages(): catalog = translation_catalogs(fallback) msg = catalog.ugettext(force_text(vo.msgid)) if self.to_python(msg) != vo.msgid: vo.fallback = self.to_python(msg) break elif vo.fallback: # if a valid fallback is found, then, since the msg is equal # to the msgid, the fallback is the winner. vo.msg = vo.fallback # if we came here we collected data from the catalog and we should return # a standin. A standin is the return value, with some extra properties. # see GettextVO for the extra properties added. if valid_for_gettext(vo.stored_value): vo.standin_value_is_from_database = True # database always wins return standin_for(vo.stored_value, **vo.__dict__) elif valid_for_gettext(vo.msg): # runner up is the translation in the native language return standin_for(vo.msg, **vo.__dict__) elif valid_for_gettext(vo.fallback): # and last is the translation in a fallback language return standin_for(vo.fallback, **vo.__dict__) assert (valid_for_gettext(vo.msgid)) # there is a very very small probability that the translation of # a valid msgid evaluates to empty string or None (after to_python). # If that happened, we got here. I choose to return None, because i like # to be like that return None
def predicate(x): field_name = get_real_fieldname(field, x) if hasattr(context, field_name): return field_name return None
def predicate(x): value = getattr(context, get_real_fieldname(field, x), None) return value if valid_for_gettext(value) else None
def __call__(self, cls): """run the filter on the class to be decorated""" if hasattr(cls, 'model'): self.model = cls.model elif not self.model: raise AttributeError(L10n.error_no_model % (cls.__name__)) # gather names of fields added by I18n added_fields = [] for field in self.model.localized_fields: for language in get_all_language_codes(): added_fields.append(get_real_fieldname(field, language)) # hide added fields from form and admin cls.exclude = added_fields cls.form = forms.make_localised_form(self.model, cls.form, exclude=added_fields) # determine name of the permission to edit untranslated fields permisson_name = "%s.can_edit_untranslated_fields_of_%s" % ( self.model._meta.app_label, self.model.__name__.lower()) # make sure that fields become read_only when no permission is given. def get_readonly_fields(self, request, obj=None): if not request.user.has_perm(permisson_name): fields = self.fields or map(lambda x: x.name, self.model._meta.fields) # remove primary key because we don't show that, not even uneditable fields.pop(self.model._meta.pk_index()) prohibited_fields = compute_prohibited( fields, self.exclude, self.model.localized_fields) return set(self.readonly_fields).union(prohibited_fields) return self.readonly_fields cls.get_readonly_fields = get_readonly_fields # override some views to hide fields which are not localized if hasattr(cls, 'change_view'): # BaseModelAdmin.__init__ will mess up our lazy lists if the following is # not allready defined if 'action_checkbox' not in cls.list_display and cls.actions is not None: cls.list_display = ['action_checkbox'] + list(cls.list_display) if not cls.list_display_links: for name in cls.list_display: if name != 'action_checkbox': cls.list_display_links = [name] break # Make certain properties lazy and internationalized cls.list_display_links = lazy_localized_list( cls.list_display_links, self.model.localized_fields) cls.list_display = lazy_localized_list(cls.list_display, self.model.localized_fields) cls.list_editable = lazy_localized_list( cls.list_editable, self.model.localized_fields) cls.search_fields = lazy_localized_list( cls.search_fields, self.model.localized_fields) else: def get_formset(self, request, obj=None, **kwargs): if self.declared_fieldsets: fields = flatten_fieldsets(self.declared_fieldsets) else: fields = None if self.exclude is None: exclude = [] else: exclude = list(self.exclude) exclude.extend(self.get_readonly_fields(request, obj)) exclude = exclude or None if not hasattr(self, 'ct_fk_field'): return super(cls, self).get_formset(request, obj, **kwargs) else: # TODO: # this code can be deleted if django fixes GenericInlineModelAdmin it's # get_formset signature so it looks like InlineModelAdmin defaults = { "ct_field": self.ct_field, "fk_field": self.ct_fk_field, "form": self.form, "formfield_callback": self.formfield_for_dbfield, "formset": self.formset, "extra": self.extra, "can_delete": self.can_delete, "can_order": False, "fields": fields, "max_num": self.max_num, "exclude": exclude } defaults.update(kwargs) # the BaseGenericInlineFormSet does not work too well # with modified models, so use LocalizableGenericInlineFormSet. if self.formset is BaseGenericInlineFormSet \ or self.formset.__class__ is BaseGenericInlineFormSet: defaults['formset'] = LocalizableGenericInlineFormSet return generic_inlineformset_factory( self.model, **defaults) cls.get_formset = get_formset return cls