def test_sitemap_lang_slug(self): """Test that the url is in the locale language""" site = Site.objects.get_current() from modeltranslation.utils import build_localized_fieldname # pylint: disable=F0401 kwargs1 = {} kwargs2 = {} for lang_code, lang_name in settings.LANGUAGES: loc_title_var = build_localized_fieldname('title', lang_code) loc_slug_var = build_localized_fieldname('slug', lang_code) kwargs1[loc_title_var] = 'article-{0}-1'.format(lang_name) kwargs2[loc_title_var] = 'other-article-{0}-2'.format(lang_name) kwargs1[loc_slug_var] = slugify(kwargs1[loc_title_var]) kwargs2[loc_slug_var] = slugify(kwargs2[loc_title_var]) article_class = get_article_class() article1 = mommy.make(article_class, publication=BaseArticle.PUBLISHED, **kwargs1) article2 = mommy.make(article_class, publication=BaseArticle.PUBLISHED, **kwargs2) factory = RequestFactory() request = factory.get('/sitemap.xml') response = sitemap_view(request, get_sitemaps()) self.assertEqual(200, response.status_code) for (lang, name) in settings.LANGUAGES: activate(lang) self.assertContains(response, site.domain + article1.get_absolute_url()) self.assertContains(response, site.domain + article2.get_absolute_url())
def handle(self, *args, **options): """command""" #look for emailing to be sent verbose = options.get('verbosity', 1) if not is_localized(): print("the site is not localized this is not required") from modeltranslation.utils import build_localized_fieldname for alias in Alias.objects.all(): cursor = connection.cursor() cursor.execute( '''SELECT path, redirect_url FROM coop_cms_alias where id={0}'''.format(alias.id)) row = cursor.fetchone() print(row) (path, redirect_url) = row languages = [x for (x, y) in settings.LANGUAGES] lang_code = languages[0] path_field_name = build_localized_fieldname('path', lang_code) redirect_url_field_name = build_localized_fieldname('redirect_url', lang_code) if (not getattr(alias, path_field_name)) and (not getattr(alias, redirect_url_field_name)): print("update", alias.id, path, redirect_url) setattr(alias, path_field_name, path) setattr(alias, redirect_url_field_name, redirect_url) alias.save()
def _patch_prepopulated_fields(self): if self.prepopulated_fields: # prepopulated_fields_new = dict(self.prepopulated_fields) # for (k, v) in self.prepopulated_fields.items(): # if v[0] in self.trans_opts.fields: # translation_fields = get_translation_fields(v[0]) # prepopulated_fields_new[k] = tuple([translation_fields[0]]) prepopulated_fields_new = {} for (k, v) in self.prepopulated_fields.items(): if k in self.trans_opts.fields: translation_fields = [(l, build_localized_fieldname(k, l)) for l in AVAILABLE_LANGUAGES] new_keys = tuple(translation_fields) else: new_keys = ((None, k),) for lang, new_key in new_keys: languages = AVAILABLE_LANGUAGES if lang: languages = [lang] new_vals = [] for val in v: if val in self.trans_opts.fields: translation_fields = [build_localized_fieldname(val, l) for l in languages] new_vals.extend(translation_fields) else: new_vals.append(val) new_vals = tuple(new_vals) prepopulated_fields_new[new_key] = new_vals self.prepopulated_fields = prepopulated_fields_new
def modeltranslation_update_slugs(sender, **kwargs): # https://bitbucket.org/neithere/django-autoslug/pull-request/11/modeltranslation-support-fix-issue-19/ # http://django-modeltranslation.readthedocs.org # # TODO: tests # if not modeltranslation_utils: return instance = kwargs['instance'] slugs = {} for field in instance._meta.fields: if type(field) == AutoSlugField: for lang in settings.LANGUAGES: lang_code = lang[0] lang_code = lang_code.replace('-', '_') populate_from_localized = modeltranslation_utils.build_localized_fieldname(field.populate_from, lang_code) populate_from_value = getattr(instance, populate_from_localized) field_name_localized = modeltranslation_utils.build_localized_fieldname(field.name, lang_code) field_value = getattr(instance, field_name_localized) if not field_value or field.always_update: slug = field.slugify(populate_from_value) slugs[field_name_localized] = slug sender.objects.filter(pk=instance.pk).update(**slugs)
def _new_set_url_path(self, parent): """ This method override populates url_path for each specified language. This way we can get different urls for each language, defined by page slug. """ for language in mt_settings.AVAILABLE_LANGUAGES: localized_slug_field = build_localized_fieldname('slug', language) default_localized_slug_field = build_localized_fieldname('slug', mt_settings.DEFAULT_LANGUAGE) localized_url_path_field = build_localized_fieldname('url_path', language) default_localized_url_path_field = build_localized_fieldname('url_path', mt_settings.DEFAULT_LANGUAGE) if parent: parent = parent.specific # Emulate the default behavior of django-modeltranslation to get the slug and url path # for the current language. If the value for the current language is invalid we get the one # for the default fallback language slug = getattr(self, localized_slug_field, None) or getattr(self, default_localized_slug_field, self.slug) parent_url_path = getattr(parent, localized_url_path_field, None) or \ getattr(parent, default_localized_url_path_field, parent.url_path) setattr(self, localized_url_path_field, parent_url_path + slug + '/') else: # a page without a parent is the tree root, # which always has a url_path of '/' setattr(self, localized_url_path_field, '/') # update url_path for children pages for child in self.get_children().specific(): child.set_url_path(self.specific) child.save() return self.url_path
def populate_translation_fields(sender, kwargs): """ When models are created or loaded from fixtures, replicates values provided for translatable fields to some / all empty translation fields, according to the current population mode. Population is performed only on keys (field names) present in kwargs. Nothing is returned, but passed kwargs dictionary is altered. With ``mode`` set to: -- ``all``: fills all translation fields, skipping just those for which a translated value is also provided; -- ``default``: fills only the default translation (unless it is additionally provided); -- ``required``: like ``default``, but only if the original field is non-nullable; At least the ``required`` mode should be used when loading untranslated fixtures to keep the database consistent (note that Django management commands are normally forced to run with hardcoded ``en-us`` language active). The ``default`` mode is useful if you need to ensure fallback values are available, and ``all`` if you need to have all translations defined (for example to make lookups / filtering without resorting to query fallbacks). """ populate = mt_settings.AUTO_POPULATE if not populate: return if populate is True: # What was meant by ``True`` is now called ``all``. populate = 'all' opts = translator.get_options_for_model(sender) for key, val in list(kwargs.items()): if key in opts.fields: if populate == 'all': # Set the value for every language. for translation_field in opts.fields[key]: kwargs.setdefault(translation_field.name, val) elif populate == 'default': default = build_localized_fieldname(key, mt_settings.DEFAULT_LANGUAGE) kwargs.setdefault(default, val) elif populate == 'required': default = build_localized_fieldname(key, mt_settings.DEFAULT_LANGUAGE) if not sender._meta.get_field(key).null: kwargs.setdefault(default, val) else: raise AttributeError("Unknown population mode '%s'." % populate)
def get_attname_column(self): attname = self.get_attname() if self.translated_field.db_column: column = build_localized_fieldname(self.translated_field.db_column) else: column = attname return attname, column
def __init__(self): super(Command, self).__init__() update_fields = ['url_path'] for language in mt_settings.AVAILABLE_LANGUAGES: localized_url_path = build_localized_fieldname('url_path', language) update_fields.append(localized_url_path) self.update_fields = update_fields
def __get__(self, instance, owner): """ Returns value from the translation field for the current language, or value for some another language according to fallback languages, or the custom fallback value, or field's default value. """ if instance is None: return self default = NONE undefined = self.fallback_undefined if undefined is NONE: default = self.field.get_default() undefined = default langs = resolution_order(get_language(), self.fallback_languages) for lang in langs: loc_field_name = build_localized_fieldname(self.field.name, lang) val = getattr(instance, loc_field_name, None) if val is not None and val != undefined: return val if mt_settings.ENABLE_FALLBACKS and self.fallback_value is not NONE: return self.fallback_value else: if default is NONE: default = self.field.get_default() return default
def add_translation_fields(model, opts): """ Monkey patches the original model class to provide additional fields for every language. Adds newly created translation fields to the given translation options. """ model_empty_values = getattr(opts, 'empty_values', NONE) for field_name in opts.local_fields.keys(): field_empty_value = parse_field(model_empty_values, field_name, NONE) for l in mt_settings.AVAILABLE_LANGUAGES: # Create a dynamic translation field translation_field = create_translation_field( model=model, field_name=field_name, lang=l, empty_value=field_empty_value) # Construct the name for the localized field localized_field_name = build_localized_fieldname(field_name, l) # Check if the model already has a field by that name if hasattr(model, localized_field_name): raise ValueError( "Error adding translation field. Model '%s' already contains a field named" "'%s'." % (model._meta.object_name, localized_field_name)) # This approach implements the translation fields as full valid # django model fields and therefore adds them via add_to_class model.add_to_class(localized_field_name, translation_field) opts.add_translation_field(field_name, translation_field) # Rebuild information about parents fields. If there are opts.local_fields, field cache would be # invalidated (by model._meta.add_field() function). Otherwise, we need to do it manually. if len(opts.local_fields) == 0: model._meta._fill_fields_cache()
def _validate_slugs(page): """ Determine whether the given slug is available for use on a child page of parent_page. """ parent_page = page.get_parent() if parent_page is None: # the root page's slug can be whatever it likes... return {} # Save the current active language current_language = get_language() siblings = page.get_siblings(inclusive=False).specific() errors = {} for language in mt_settings.AVAILABLE_LANGUAGES: # Temporarily activate every language because even though there might # be no repeated value for slug_pt the fallback of an empty slug could # already be in use trans_real.activate(language) siblings_slugs = [sibling.slug for sibling in siblings] if page.specific.slug in siblings_slugs: errors[build_localized_fieldname('slug', language)] = _("This slug is already in use") # Re-enable the original language trans_real.activate(current_language) return errors
def get_attname_column(self): attname = self.get_attname() column = ( build_localized_fieldname(self.translated_field.db_column or self.translated_field.name, self.language) or attname ) return attname, column
def __init__(self, translated_field, language, *args, **kwargs): # Update the dict of this field with the content of the original one # This might be a bit radical?! Seems to work though... self.__dict__.update(translated_field.__dict__) # Store the originally wrapped field for later self.translated_field = translated_field self.language = language # Translation are always optional (for now - maybe add some parameters # to the translation options for configuring this) if not isinstance(self, fields.BooleanField): # TODO: Do we really want to enforce null *at all*? Shouldn't this # better honour the null setting of the translated field? self.null = True self.blank = True # Adjust the name of this field to reflect the language self.attname = build_localized_fieldname(self.translated_field.name, self.language) self.name = self.attname # Copy the verbose name and append a language suffix # (will show up e.g. in the admin). self.verbose_name = build_localized_verbose_name(translated_field.verbose_name, language)
def save(self): """ Save each of the settings to the DB. """ active_language = get_language() for (name, value) in self.cleaned_data.items(): if name not in registry: name, code = name.rsplit('_modeltranslation_', 1) else: code = None setting_obj, created = Setting.objects.get_or_create(name=name) if settings.USE_MODELTRANSLATION: if registry[name]["translatable"]: try: activate(code) except: pass finally: setting_obj.value = value activate(active_language) else: # Duplicate the value of the setting for every language for code in OrderedDict(settings.LANGUAGES): setattr(setting_obj, build_localized_fieldname('value', code), value) else: setting_obj.value = value setting_obj.save()
def __init__(self, translated_field, language, *args, **kwargs): # Store the originally wrapped field for later self.translated_field = translated_field self.language = language # Update the dict of this field with the content of the original one # This might be a bit radical?! Seems to work though... self.__dict__.update(translated_field.__dict__) # Translation are always optional (for now - maybe add some parameters # to the translation options for configuring this) self.null = True self.blank = True # If translations are optional, uniqueness can't be enforced self._unique = False # Adjust the name of this field to reflect the language self.attname = build_localized_fieldname(translated_field.name, language) self.name = self.attname # Copy the verbose name and append a language suffix (will e.g. in the # admin). This might be a proxy function so we have to check that here. if hasattr(translated_field.verbose_name, '_proxy____unicode_cast'): verbose_name = translated_field.verbose_name._proxy____unicode_cast() else: verbose_name = translated_field.verbose_name self.verbose_name = '%s [%s]' % (verbose_name, language)
def add_localized_fields(model): """ Monkey patches 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: # Create a dynamic translation field translation_field = create_translation_field( model=model, field_name=field_name, lang=l[0]) # 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. Model '%s' already " "contains a field named '%s'." % ( model._meta.object_name, localized_field_name)) # This approach implements the translation fields as full valid # django model fields and therefore adds them via add_to_class model.add_to_class(localized_field_name, translation_field) localized_fields[field_name].append(localized_field_name) return localized_fields
def cache_name(self): """ Used in django 1.x """ lang = get_language() cache = build_localized_fieldname(self.accessor, lang) return "_%s_cache" % cache
def __get__(self, instance, owner): """ Returns value from the translation field for the current language, or value for some another language according to fallback languages, or the custom fallback value, or field's default value. """ if instance is None: return self default = NONE undefined = self.fallback_undefined if undefined is NONE: default = self.field.get_default() undefined = default langs = resolution_order(get_language(), self.fallback_languages) for lang in langs: loc_field_name = build_localized_fieldname(self.field.name, lang) val = getattr(instance, loc_field_name, None) if self.meaningful_value(val, undefined): return val if mt_settings.ENABLE_FALLBACKS and self.fallback_value is not NONE: return self.fallback_value else: if default is NONE: default = self.field.get_default() # Some fields like FileField behave strange, as their get_default() doesn't return # instance of attr_class, but rather None or ''. # Normally this case is handled in the descriptor, but since we have overridden it, we # must mock it up. if (isinstance(self.field, fields.files.FileField) and not isinstance(default, self.field.attr_class)): return self.field.attr_class(instance, self.field, default) return default
def _patch_simple_panel(self, model, original_panel): panel_class = original_panel.__class__ translated_panels = [] translation_registered_fields = translator.get_options_for_model(model).fields # If the panel field is not registered for translation # the original one is returned if original_panel.field_name not in translation_registered_fields: return [original_panel] for language in mt_settings.AVAILABLE_LANGUAGES: original_field = model._meta.get_field(original_panel.field_name) localized_field_name = build_localized_fieldname(original_panel.field_name, language) # if the original field is required and the current language is the default one # this field's blank property is set to False if not original_field.blank and language == mt_settings.DEFAULT_LANGUAGE: localized_field = model._meta.get_field(localized_field_name) localized_field.blank = False localized_panel = panel_class(localized_field_name) # Pass the original panel extra attributes to the localized if hasattr(original_panel, 'classname'): localized_panel.classname = original_panel.classname if hasattr(original_panel, 'widget'): localized_panel.widget = original_panel.widget translated_panels.append(localized_panel) return translated_panels
def rewrite_lookup_key(model, lookup_key): translatable_fields = get_translatable_fields_for_model(model) if translatable_fields is not None: pieces = lookup_key.split('__') # If we are doing a lookup on a translatable field, # we want to rewrite it to the actual field name # For example, we want to rewrite "name__startswith" to "name_fr__startswith" if pieces[0] in translatable_fields: lookup_key = build_localized_fieldname(pieces[0], get_language()) remaining_lookup = '__'.join(pieces[1:]) if remaining_lookup: lookup_key = '%s__%s' % (lookup_key, remaining_lookup) pieces = lookup_key.split('__') if len(pieces) > 1: # Check if we are doing a lookup to a related trans model fields_to_trans_models = get_fields_to_translatable_models(model) for field_to_trans, transmodel in fields_to_trans_models: if pieces[0] == field_to_trans: sub_lookup = '__'.join(pieces[1:]) if sub_lookup: sub_lookup = rewrite_lookup_key(transmodel, sub_lookup) lookup_key = '%s__%s' % (pieces[0], sub_lookup) break return lookup_key
def _update_translation_descendant_url_paths(old_record, page): # update children paths, must be done for all languages to ensure fallbacks are applied languages_changed = [] default_localized_url_path = build_localized_fieldname('url_path', mt_settings.DEFAULT_LANGUAGE) for language in mt_settings.AVAILABLE_LANGUAGES: localized_url_path = build_localized_fieldname('url_path', language) old_url_path = getattr(old_record, localized_url_path) or getattr(old_record, default_localized_url_path) new_url_path = getattr(page, localized_url_path) or getattr(page, default_localized_url_path) if old_url_path == new_url_path: # nothing to do continue languages_changed.append(language) _localized_update_descendant_url_paths(page, old_url_path, new_url_path, language) _update_untranslated_descendants_url_paths(page, languages_changed)
def __set__(self, instance, value): lang = get_language() loc_field_name = build_localized_fieldname(self.field_name, lang) # Localized field name with '_id' loc_attname = instance._meta.get_field(loc_field_name).get_attname() setattr(instance, loc_attname, value) base_attname = instance._meta.get_field(self.field_name).get_attname() instance.__dict__[base_attname] = value
def __init__(self, translated_field, language, empty_value, *args, **kwargs): # Update the dict of this field with the content of the original one # This might be a bit radical?! Seems to work though... self.__dict__.update(translated_field.__dict__) # Store the originally wrapped field for later self.translated_field = translated_field self.language = language self.empty_value = empty_value if empty_value is NONE: self.empty_value = None if translated_field.null else "" # Translation are always optional (for now - maybe add some parameters # to the translation options for configuring this) if not isinstance(self, fields.BooleanField): # TODO: Do we really want to enforce null *at all*? Shouldn't this # better honour the null setting of the translated field? self.null = True self.blank = True # Adjust the name of this field to reflect the language self.attname = build_localized_fieldname(self.translated_field.name, self.language) self.name = self.attname # Copy the verbose name and append a language suffix # (will show up e.g. in the admin). self.verbose_name = build_localized_verbose_name(translated_field.verbose_name, language) # ForeignKey support - rewrite related_name if self.rel and self.related and not self.rel.is_hidden(): import copy current = self.related.get_accessor_name() self.rel = copy.copy(self.rel) # Since fields cannot share the same rel object. # self.related doesn't need to be copied, as it will be recreated in # ``RelatedField.do_related_class`` if self.rel.related_name is None: # For implicit related_name use different query field name loc_related_query_name = build_localized_fieldname(self.related_query_name(), self.language) self.related_query_name = lambda: loc_related_query_name self.rel.related_name = build_localized_fieldname(current, self.language) self.rel.field = self # Django 1.6 if hasattr(self.rel.to._meta, "_related_objects_cache"): del self.rel.to._meta._related_objects_cache
def get_missing_languages(self, field_name, db_table): """ Gets only missings fields. """ db_table_fields = self.get_table_fields(db_table) for lang_code, lang_name in settings.LANGUAGES: if build_localized_fieldname(field_name, lang_code) not in db_table_fields: yield lang_code
def mldict(self, field_name): data = {} for lang in AVAILABLE_LANGUAGES: localized_field_name = build_localized_fieldname(field_name, lang) value = getattr(self, localized_field_name) if lang == DEFAULT_LANGUAGE or value: data[lang] = value return data
def __set__(self, instance, value): lang = get_language() loc_field_name = build_localized_fieldname(self.name, lang) # also update the translation field of the current language setattr(instance, loc_field_name, value) # update the original field via the __dict__ to prevent calling the # descriptor instance.__dict__[self.name] = value
def _patch_page_models(self, model): # PANEL PATCHING # Check if the model has a custom edit handler if hasattr(model, 'edit_handler'): tabs = model.edit_handler.children for tab in tabs: tab.children = self._patch_panels(tab.children) else: # If the page doesn't have an edit_handler we patch the panels that # wagtail uses by default if hasattr(model, 'content_panels'): model.content_panels = self._patch_panels(model.content_panels) if hasattr(model, 'promote_panels'): model.promote_panels = self._patch_panels(model.promote_panels) if hasattr(model, 'settings_panels'): model.settings_panels = self._patch_panels(model.settings_panels) # Clear the edit handler cached value, if it exists, so wagtail reconstructs # the edit_handler based on the patched panels model.get_edit_handler.cache_clear() # SEARCH FIELDS PATCHING translation_registered_fields = translator.get_options_for_model(model).fields for field in model.search_fields: # Check if the field is a SearchField and if it is one of the fields registered for translation if field.__class__ is SearchField and field.field_name in translation_registered_fields: # If it is we create a clone of the original SearchField to keep all the defined options # and replace its name by the translated one for language in mt_settings.AVAILABLE_LANGUAGES: translated_field = copy.deepcopy(field) translated_field.field_name = build_localized_fieldname(field.field_name, language) model.search_fields = list(model.search_fields) + [translated_field] # OVERRIDE FIELDS model_fields = model._meta.get_fields() for field in model_fields: if isinstance(field, StreamField) and field.name in translation_registered_fields: descriptor = getattr(model, field.name) _patch_stream_field_meaningful_value(descriptor) # OVERRIDE PAGE METHODS if TRANSLATE_SLUGS: model.set_url_path = _new_set_url_path model.route = _new_route model._update_descendant_url_paths = _new_update_descendant_url_paths if not hasattr(model, '_get_site_root_paths'): model.get_url_parts = _new_get_url_parts # Wagtail<1.11 model._get_site_root_paths = _new_get_site_root_paths _patch_clean(model) if not model.save.__name__.startswith('localized'): setattr(model, 'save', LocalizedSaveDescriptor(model.save))
def __set__(self, instance, value): if getattr(instance, '_mt_init', False): # When assignment takes place in model instance constructor, don't set value. # This is essential for only/defer to work, but I think it's sensible anyway. return lang = get_language() loc_field_name = build_localized_fieldname(self.field.name, lang) # also update the translation field of the current language setattr(instance, loc_field_name, value)
def __call__(self, instance, *args, **kwargs): # when updating, save doesn't check if slug_xx has changed so it can only detect changes in slug # from current language. We need to ensure that if a given localized slug changes we call set_url_path if not instance.id: # creating a record, wagtail will call set_url_path, nothing to do. return self.func(instance, *args, **kwargs) old_record = None change_url_path = change_descendant_url_path = False for language in mt_settings.AVAILABLE_LANGUAGES: localized_slug = build_localized_fieldname('slug', language) # similar logic used in save if not ('update_fields' in kwargs and localized_slug not in kwargs['update_fields']): old_record = old_record or Page.objects.get(id=instance.id) if getattr(old_record, localized_slug) != getattr(instance, localized_slug): change_descendant_url_path = True if language != get_language(): change_url_path = True break # Pages may have have their url_path_xx changed upstream when a parent has its url_path changed. # If that's the case let's propagate the change to children if not change_descendant_url_path: localized_url_path = build_localized_fieldname('url_path', language) if not ('update_fields' in kwargs and localized_url_path not in kwargs['update_fields']): old_record = old_record or Page.objects.get(id=instance.id) if getattr(old_record, localized_url_path) != getattr(instance, localized_url_path): change_descendant_url_path = True # if any language other than current language had it slug changed set_url_path will be executed if change_url_path: instance.set_url_path(instance.get_parent()) result = self.func(instance, *args, **kwargs) # update children localized paths if any language had it slug changed if change_descendant_url_path: _update_translation_descendant_url_paths(old_record, instance) # Check if this is a root page of any sites and clear the 'wagtail_site_root_paths_XX' key if so if Site.objects.filter(root_page=instance).exists(): for language in mt_settings.AVAILABLE_LANGUAGES: cache.delete('wagtail_site_root_paths_{}'.format(language)) return result
def get_slug(self): """ Allows subclasses to implement their own slug creation logic. """ attr = "title" if settings.USE_MODELTRANSLATION: from modeltranslation.utils import build_localized_fieldname attr = build_localized_fieldname(attr, settings.LANGUAGE_CODE) # Get self.title_xx where xx is the default language, if any. # Get self.title otherwise. return slugify(getattr(self, attr, None) or self.title)
def rewrite_lookup_key(model, lookup_key): pieces = lookup_key.split('__', 1) original_key = pieces[0] translatable_fields = get_translatable_fields_for_model(model) if translatable_fields is not None: # If we are doing a lookup on a translatable field, # we want to rewrite it to the actual field name # For example, we want to rewrite "name__startswith" to "name_fr__startswith" if pieces[0] in translatable_fields: pieces[0] = build_localized_fieldname(pieces[0], get_language()) if len(pieces) > 1: # Check if we are doing a lookup to a related trans model fields_to_trans_models = get_fields_to_translatable_models(model) for field_to_trans, transmodel in fields_to_trans_models: # Check ``original key``, as pieces[0] may have been already rewritten. if original_key == field_to_trans: pieces[1] = rewrite_lookup_key(transmodel, pieces[1]) break return '__'.join(pieces)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) required_fields = getattr(self.instance, "required_translation_fields", []) if not required_fields: required_fields = [ f.name for f in self.instance._meta.local_fields if hasattr(f, "blank") and f.blank is False ] required_fields = set(required_fields + [ 'title', 'slug', ]) for lang_code in mt_settings.AVAILABLE_LANGUAGES: for field in required_fields: localized = build_localized_fieldname(field, lang_code) if localized in self.fields: self.fields[localized].label = "{}*".format( self.fields[localized].label)
def _update_untranslated_descendants_url_paths(page, languages_changed): """ Updates localized URL Paths for child pages that don't have their localized URL Paths set yet """ if not languages_changed: return condition = Q() update_fields = [] for language in languages_changed: localized_url_path = build_localized_fieldname('url_path', language) condition |= Q(**{localized_url_path: None}) update_fields.append(localized_url_path) # let's restrict the query to children who don't have localized_url_path set yet children = page.get_children().filter(condition) for child in children: for language in languages_changed: _localized_set_url_path(child, page, language) child.save(update_fields=update_fields ) # this will trigger any required saves downstream
def test_required_for_language(translated_page, settings, rf): edit_handler = translated_page.get_edit_handler() form_class = edit_handler.get_form_class() form = form_class() edit_handler.bind_to_instance(instance=translated_page, form=form, request=rf) fields = translated_page.get_required_translatable_fields() for name, (code, _) in itertools.product(fields, settings.LANGUAGES): field_name = build_localized_fieldname(name, lang=code) field = form_class.base_fields[field_name] if code == settings.LANGUAGE_CODE: assert field.required is True else: assert field.required is False assert field.widget.attrs['required_for_language'] is True assert field.widget.attrs['class'] == ' required-for-language'
def _patch_prepopulated_fields(self): def localize(sources, lang): "Append lang suffix (if applicable) to field list" def append_lang(source): if source in self.trans_opts.fields: return build_localized_fieldname(source, lang) return source return tuple(map(append_lang, sources)) prepopulated_fields = {} for dest, sources in self.prepopulated_fields.items(): if dest in self.trans_opts.fields: for lang in mt_settings.AVAILABLE_LANGUAGES: key = build_localized_fieldname(dest, lang) prepopulated_fields[key] = localize(sources, lang) else: lang = mt_settings.PREPOPULATE_LANGUAGE or get_language() prepopulated_fields[dest] = localize(sources, lang) self.prepopulated_fields = prepopulated_fields
def add_translation_fields(model, opts): """ Monkey patches the original model class to provide additional fields for every language. Adds newly created translation fields to the given translation options. """ model_empty_values = getattr(opts, 'empty_values', NONE) for field_name in opts.local_fields.keys(): field_empty_value = parse_field(model_empty_values, field_name, NONE) for lang in mt_settings.AVAILABLE_LANGUAGES: # Create a dynamic translation field translation_field = create_translation_field( model=model, field_name=field_name, lang=lang, empty_value=field_empty_value ) # Construct the name for the localized field localized_field_name = build_localized_fieldname(field_name, lang) # Check if the model already has a field by that name if hasattr(model, localized_field_name): # Check if are not dealing with abstract field inherited. for cls in model.__mro__: if hasattr(cls, '_meta') and cls.__dict__.get(localized_field_name, None): cls_opts = translator._get_options_for_model(cls) if not cls._meta.abstract or field_name not in cls_opts.local_fields: raise ValueError( "Error adding translation field. Model '%s' already" " contains a field named '%s'." % (model._meta.object_name, localized_field_name) ) # This approach implements the translation fields as full valid # django model fields and therefore adds them via add_to_class model.add_to_class(localized_field_name, translation_field) opts.add_translation_field(field_name, translation_field) # Rebuild information about parents fields. If there are opts.local_fields, field cache would be # invalidated (by model._meta.add_field() function). Otherwise, we need to do it manually. if len(opts.local_fields) == 0: model._meta._expire_cache() model._meta.get_fields()
def __mangle_field(self, name): model = self._model results = field_mangling.send(sender=self, field=name, model=model) results = [i[1] for i in results if i[1]] if results: # response in format tuple(priority: int, mangled_field_name: str) results.sort(key=lambda x: x[0], reverse=True) # Sort by priority return results[0][1] # Backward compatibility. Deprecated: result = { 'field': name, } field_conversion.send(sender=self, result=result, field=name, model=model) mangled_field_name = result['field'] if mangled_field_name != name: return mangled_field_name # django-multilingual-ext support if 'modeltranslation' in settings.INSTALLED_APPS: from modeltranslation.translator import translator, NotRegistered from modeltranslation.utils import get_language, build_localized_fieldname else: translator = None if translator: try: trans_opts = translator.get_options_for_model(model) if name in trans_opts.fields: return build_localized_fieldname(name, get_language()) except NotRegistered: pass if hasattr(model.objects, 'localize_fieldname'): return model.objects.localize_fieldname(name) return name
def _post_init(self, translated_field, language): """ Common init for subclasses of TranslationField. """ # Store the originally wrapped field for later self.translated_field = translated_field self.language = language # Translation are always optional (for now - maybe add some parameters # to the translation options for configuring this) self.null = True self.blank = True # Adjust the name of this field to reflect the language self.attname = build_localized_fieldname(self.translated_field.name, self.language) self.name = self.attname # Copy the verbose name and append a language suffix # (will show up e.g. in the admin). self.verbose_name = build_localized_verbose_name( translated_field.verbose_name, language)
def _build_occurrence(self, occurence): """ Args: occurence: a tuple of class path and primary key Returns: a tuple of the class and the primary key """ from modeltranslation.utils import build_localized_fieldname dotted_path, pk = occurence module_name, class_name, field_name = dotted_path.rsplit(".", 2) module = importlib.import_module(module_name) field_name = build_localized_fieldname(field_name, self.language) try: model_class = getattr(module, class_name) except AttributeError: model_class = apps.get_model(module_name, class_name) return model_class, field_name, pk
def _patch_simple_panel(self, model, original_panel): panel_class = original_panel.__class__ translated_panels = [] translation_registered_fields = translator.get_options_for_model(model).fields # If the panel field is not registered for translation # the original one is returned if original_panel.field_name not in translation_registered_fields: return [original_panel] for language in mt_settings.AVAILABLE_LANGUAGES: original_field = model._meta.get_field(original_panel.field_name) localized_field_name = build_localized_fieldname(original_panel.field_name, language) # if the original field is required and the current language is the default one # this field's blank property is set to False if not original_field.blank and language == mt_settings.DEFAULT_LANGUAGE: localized_field = model._meta.get_field(localized_field_name) localized_field.blank = False elif isinstance(original_field, StreamField): # otherwise the field is optional and # if it's a StreamField the stream_block need to be changed to non required localized_field = model._meta.get_field(localized_field_name) new_stream_block = copy.copy(localized_field.stream_block) new_stream_block.meta = copy.copy(localized_field.stream_block.meta) new_stream_block.meta.required = False localized_field.stream_block = new_stream_block localized_panel = panel_class(localized_field_name) # Pass the original panel extra attributes to the localized if hasattr(original_panel, 'classname'): localized_panel.classname = original_panel.classname if hasattr(original_panel, 'widget'): localized_panel.widget = original_panel.widget translated_panels.append(localized_panel) return translated_panels
def _localized_update_descendant_url_paths(page, old_url_path, new_url_path, language=None): localized_url_path = 'url_path' if language: localized_url_path = build_localized_fieldname('url_path', language) if connection.vendor in ('mssql', 'microsoft'): cursor = connection.cursor() update_statement = """ UPDATE wagtailcore_page SET {localized_url_path}= CONCAT(%s, (SUBSTRING({localized_url_path}, 0, %s))) WHERE path LIKE %s AND id <> %s """.format(localized_url_path=localized_url_path) cursor.execute(update_statement, [new_url_path, len(old_url_path) + 1, page.path + '%', page.id]) else: (Page.objects .rewrite(False) .filter(path__startswith=page.path) .exclude(**{localized_url_path: None}) # url_path_xx may not be set yet .exclude(pk=page.pk) .update(**{localized_url_path: Concat( Value(new_url_path), Substr(localized_url_path, len(old_url_path) + 1))}))
def handle(self, *args, **options): verbosity = int(options['verbosity']) if verbosity > 0: self.stdout.write("Using default language: %s\n" % DEFAULT_LANGUAGE) models = translator.get_registered_models(abstract=False) for model in models: if verbosity > 0: self.stdout.write("Updating data of model '%s'\n" % model) opts = translator.get_options_for_model(model) for field_name in opts.fields.keys(): def_lang_fieldname = build_localized_fieldname( field_name, DEFAULT_LANGUAGE) # We'll only update fields which do not have an existing value q = Q(**{def_lang_fieldname: None}) field = model._meta.get_field(field_name) if field.empty_strings_allowed: q |= Q(**{def_lang_fieldname: ""}) model._default_manager.filter(q).rewrite(False).update( **{def_lang_fieldname: F(field_name)})
def get_sync_sql(self, field_name, missing_langs, model): """ returns SQL needed for sync schema for a new translatable field """ qn = connection.ops.quote_name style = no_style() sql_output = [] db_table = model._meta.db_table for lang in missing_langs: new_field = build_localized_fieldname(field_name, lang) f = model._meta.get_field(new_field) col_type = f.db_type() field_sql = [ style.SQL_FIELD(qn(f.column)), style.SQL_COLTYPE(col_type) ] # column creation sql_output.append("ALTER TABLE %s ADD COLUMN %s;" % (qn(db_table), ' '.join(field_sql))) if not f.null and lang == settings.LANGUAGE_CODE: sql_output.append("ALTER TABLE %s MODIFY COLUMN %s %s %s;" % \ (qn(db_table), qn(f.column), col_type, \ style.SQL_KEYWORD('NOT NULL'))) return sql_output
def add_translation_fields(model, opts): """ Monkey patches the original model class to provide additional fields for every language. Adds newly created translation fields to the given translation options. """ model_empty_values = getattr(opts, 'empty_values', NONE) for field_name in opts.local_fields.keys(): field_empty_value = parse_field(model_empty_values, field_name, NONE) for l in mt_settings.AVAILABLE_LANGUAGES: # Create a dynamic translation field translation_field = create_translation_field( model=model, field_name=field_name, lang=l, empty_value=field_empty_value) # Construct the name for the localized field localized_field_name = build_localized_fieldname(field_name, l) # Check if the model already has a field by that name if hasattr(model, localized_field_name): raise ValueError( "Error adding translation field. Model '%s' already contains a field named" "'%s'." % (model._meta.object_name, localized_field_name)) # This approach implements the translation fields as full valid # django model fields and therefore adds them via add_to_class model.add_to_class(localized_field_name, translation_field) opts.add_translation_field(field_name, translation_field) # Rebuild information about parents fields. If there are opts.local_fields, field cache would be # invalidated (by model._meta.add_field() function). Otherwise, we need to do it manually. if len(opts.local_fields) == 0: try: model._meta._fill_fields_cache() except AttributeError: # Django 1.8 removed _fill_fields_cache model._meta._expire_cache() model._meta.get_fields()
def __get__(self, instance, owner): """ Returns value from the translation field for the current language, or value for some another language according to fallback languages, or the custom fallback value, or field's default value. """ if instance is None: return self from modeltranslation.translator import translator current_language = get_language() default = NONE undefined = self.fallback_undefined if undefined is NONE: default = self.field.get_default() undefined = default langs = resolution_order(current_language, self.fallback_languages) for lang in langs: loc_field_name = build_localized_fieldname(self.field.name, lang) val = getattr(instance, loc_field_name, None) if self.meaningful_value(val, undefined): if lang != current_language and self.field.get_internal_type() in ["TextField", "CharField"]: opts = translator.get_options_for_model(self.field.model) if hasattr(opts, "fallback_prepend") and self.field.name in opts.fallback_prepend: val = "{}{}".format(opts.fallback_prepend[self.field.name], val) return val if mt_settings.ENABLE_FALLBACKS and self.fallback_value is not NONE: return self.fallback_value else: if default is NONE: default = self.field.get_default() # Some fields like FileField behave strange, as their # get_default() doesn't return instance of attr_class, but rather # None or ''. Normally this case is handled in the descriptor, but # since we have overridden it, we must mock it up. if (isinstance(self.field, fields.files.FileField) and not isinstance(default, self.field.attr_class)): return self.field.attr_class(instance, self.field, default) return default
def get_sync_sql(self, field_name, missing_langs, model): """ Returns SQL needed for sync schema for a new translatable field. """ qn = connection.ops.quote_name style = no_style() sql_output = [] db_table = model._meta.db_table for lang in missing_langs: new_field = build_localized_fieldname(field_name, lang) f = model._meta.get_field(new_field) col_type = f.db_type(connection=connection) field_sql = [ style.SQL_FIELD(qn(f.column)), style.SQL_COLTYPE(col_type) ] # column creation stmt = "ALTER TABLE %s ADD COLUMN %s" % (qn(db_table), ' '.join(field_sql)) if not f.null: stmt += " " + style.SQL_KEYWORD('NOT NULL') sql_output.append(stmt + ";") return sql_output
def _localized_update_descendant_url_paths(self, old_url_path, new_url_path, language): localized_url_path = build_localized_fieldname('url_path', language) for db_table in WagtailTranslator._page_fields_tables: cursor = connection.cursor() if connection.vendor == 'sqlite': update_statement = """ UPDATE {db_table} SET {localized_url_path} = %s || substr({localized_url_path}, %s) WHERE EXISTS (SELECT * FROM wagtailcore_page AS p WHERE p.id = {db_table}.page_ptr_id AND p.path LIKE %s) AND page_ptr_id <> %s """.format(db_table=db_table, localized_url_path=localized_url_path) elif connection.vendor == 'mysql': update_statement = """ UPDATE {db_table} t JOIN wagtailcore_page p ON p.id = t.page_ptr_id SET {localized_url_path}= CONCAT(%s, substring({localized_url_path}, %s)) WHERE p.path LIKE %s AND t.page_ptr_id <> %s """.format(db_table=db_table, localized_url_path=localized_url_path) elif connection.vendor in ('mssql', 'microsoft'): update_statement = """ UPDATE t SET {localized_url_path}= CONCAT(%s, (SUBSTRING({localized_url_path}, 0, %s))) FROM {db_table} t JOIN wagtailcore_page p ON p.id = t.page_ptr_id WHERE p.path LIKE %s AND t.page_ptr_id <> %s """.format(db_table=db_table, localized_url_path=localized_url_path) else: update_statement = """ UPDATE {db_table} as t SET {localized_url_path} = %s || substring({localized_url_path} from %s) FROM wagtailcore_page AS p WHERE p.id = t.page_ptr_id AND p.path LIKE %s AND t.page_ptr_id <> %s """.format(db_table=db_table, localized_url_path=localized_url_path) cursor.execute(update_statement, [new_url_path, len(old_url_path) + 1, self.path + '%', self.page_ptr_id])
def __init__(self, *args, **kwargs): super(BaseArticleAdminForm, self).__init__(*args, **kwargs) # pylint: disable=E1002 self.article = kwargs.get('instance', None) templates = get_article_templates(self.article, getattr(self, "current_user", None)) if templates: self.fields['template'].widget = forms.Select(choices=templates) self.slug_fields = [] if is_localized(): for lang_and_name in settings.LANGUAGES: from modeltranslation.utils import build_localized_fieldname field_name = build_localized_fieldname('slug', lang_and_name[0]) self.slug_fields.append(field_name) else: self.slug_fields = ['slug'] can_change_article_slug = can_rewrite_url() if not can_change_article_slug: can_change_article_slug = (self.article.publication != BaseArticle.PUBLISHED) if self.article else True for slug_field in self.slug_fields: if not can_change_article_slug: self.fields[slug_field].widget = ReadOnlyInput()
def __init__(self, translated_field, language, *args, **kwargs): # Store the originally wrapped field for later self.translated_field = translated_field self.language = language # Update the dict of this field with the content of the original one # This might be a bit radical?! Seems to work though... self.__dict__.update(translated_field.__dict__) # Translation are always optional (for now - maybe add some parameters # to the translation options for configuring this) self.null = True self.blank = True # Adjust the name of this field to reflect the language self.attname = build_localized_fieldname(translated_field.name, language) self.name = self.attname # Copy the verbose name and append a language suffix # (will show up e.g. in the admin). self.verbose_name =\ build_localized_verbose_name(translated_field.verbose_name, language)
def set_required_for_language(form_class): """Mark fields that must be populated for a language to be available. Some fields are optional, but for their language to be available the field must be populated e.g., for German to be available for a page then the fields title_de and description_de must must be populated. `required_for_language` is used by templates to style the page and hint the user which fields they need to populate. Arguments: form_class {WagtailAdminPageForm} """ css_classname = 'required-for-language' fields = form_class._meta.model.get_required_translatable_fields() for name, (code, _) in itertools.product(fields, settings.LANGUAGES): field_name = build_localized_fieldname(name, lang=code) field = form_class.base_fields[field_name] if field.required is False: attrs = field.widget.attrs attrs['required_for_language'] = True attrs['class'] = attrs.get('class', '') + ' ' + css_classname
def add_translation_fields(model, opts): """ Monkey patches the original model class to provide additional fields for every language. Adds newly created translation fields to the given translation options. """ for field_name in opts.local_fields.keys(): for l in settings.LANGUAGES: # Create a dynamic translation field translation_field = create_translation_field(model=model, field_name=field_name, lang=l[0]) # 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. Model '%s' already contains a field named" "'%s'." % (model._meta.object_name, localized_field_name)) # This approach implements the translation fields as full valid # django model fields and therefore adds them via add_to_class model.add_to_class(localized_field_name, translation_field) opts.add_translation_field(field_name, translation_field)
def _translated(self): """ Builds a list of translation strings using the instance's target_language Returns: None """ # local import allows users w/o modeltranslations installed to build and export PO files from modeltranslation.utils import build_localized_fieldname for model_class in self.models_and_fields.keys(): class_path = ".".join( [model_class.__module__, model_class.__name__]) for instance in model_class._default_manager.all().order_by('pk'): for field_name in self.models_and_fields[model_class]: field_value = getattr(instance, field_name) or "" msgstr = getattr( instance, build_localized_fieldname(field_name, self.target_language)) or "" self.strings[field_value]['msgstr'] = msgstr self.strings[field_value]['occurrences'].append( (".".join([class_path, field_name]), instance.pk))
def _patch_page_models(self, model): # PANEL PATCHING # Check if the model has a custom edit handler if hasattr(model, 'edit_handler'): tabs = model.edit_handler.children for tab in tabs: tab.children = self._patch_panels(tab.children) else: # If the page doesn't have an edit_handler we patch the panels that # wagtail uses by default if hasattr(model, 'content_panels'): model.content_panels = self._patch_panels(model.content_panels) if hasattr(model, 'promote_panels'): model.promote_panels = self._patch_panels(model.promote_panels) if hasattr(model, 'settings_panels'): model.settings_panels = self._patch_panels( model.settings_panels) # Clear the edit handler cached value, if it exists, so wagtail reconstructs # the edit_handler based on the patched panels model.get_edit_handler.cache_clear() # SEARCH FIELDS PATCHING translation_registered_fields = translator.get_options_for_model( model).fields for field in model.search_fields: # Check if the field is a SearchField and if it is one of the fields registered for translation if isinstance( field, SearchField ) and field.field_name in translation_registered_fields: # If it is we create a clone of the original SearchField to keep all the defined options # and replace its name by the translated one for language in mt_settings.AVAILABLE_LANGUAGES: translated_field = copy.deepcopy(field) translated_field.field_name = build_localized_fieldname( field.field_name, language) model.search_fields = list( model.search_fields) + [translated_field] # OVERRIDE FIELDS model_fields = model._meta.get_fields() for field in model_fields: if isinstance(field, StreamField ) and field.name in translation_registered_fields: descriptor = getattr(model, field.name) _patch_stream_field_meaningful_value(descriptor) # OVERRIDE CLEAN METHOD model.base_form_class = patch_admin_page_form(model.base_form_class) # OVERRIDE PAGE METHODS if TRANSLATE_SLUGS: model.set_url_path = _new_set_url_path model.route = _new_route model._update_descendant_url_paths = _new_update_descendant_url_paths if not hasattr(model, '_get_site_root_paths'): model.get_url_parts = _new_get_url_parts # Wagtail<1.11 model._get_site_root_paths = _new_get_site_root_paths _patch_clean(model) if not model.save.__name__.startswith('localized'): setattr(model, 'save', LocalizedSaveDescriptor(model.save))
def cache_name(self): lang = get_language() cache = build_localized_fieldname(self.accessor, lang) return "_%s_cache" % cache
def __set__(self, instance, value): lang = get_language() loc_field_name = build_localized_fieldname(self.field_name, lang) # Localized field name with '_id' loc_attname = instance._meta.get_field(loc_field_name).get_attname() setattr(instance, loc_attname, value)
class Meta: """Meta objects.""" ordering = map(lambda (lang_code, __): build_localized_fieldname('company_name', lang_code), settings.LANGUAGES)
def __init__(self, *args, **kwargs): # CopyPage must be passed a 'page' kwarg indicating the page to be copied self.page = kwargs.pop('page') self.user = kwargs.pop('user', None) can_publish = kwargs.pop('can_publish') super(CopyForm, self).__init__(*args, **kwargs) for lang in mt_settings.AVAILABLE_LANGUAGES: new_title_field = build_localized_fieldname("new_title", lang) title_field = build_localized_fieldname("title", lang) new_slug_field = build_localized_fieldname("new_slug", lang) slug_field = build_localized_fieldname("slug", lang) if getattr(self.page, title_field): self.fields[new_title_field] = forms.CharField( initial=getattr(self.page, title_field), label=_("New title [{}]".format(lang))) self.fields[new_slug_field] = forms.SlugField( initial=getattr(self.page, slug_field), label=_("New slug [{}]".format(lang))) self.fields['new_parent_page'] = forms.ModelChoiceField( initial=self.page.get_parent(), queryset=Page.objects.all(), widget=widgets.AdminPageChooser(can_choose_root=True, user_perms='copy_to'), label=_("New parent page"), help_text=_( "This copy will be a child of this given parent page.")) pages_to_copy = self.page.get_descendants(inclusive=True) subpage_count = pages_to_copy.count() - 1 if subpage_count > 0: self.fields['copy_subpages'] = forms.BooleanField( required=False, initial=True, label=_("Copy subpages"), help_text=ungettext("This will copy %(count)s subpage.", "This will copy %(count)s subpages.", subpage_count) % {'count': subpage_count}) if can_publish: pages_to_publish_count = pages_to_copy.live().count() if pages_to_publish_count > 0: # In the specific case that there are no subpages, customise the field label and help text if subpage_count == 0: label = _("Publish copied page") help_text = _( "This page is live. Would you like to publish its copy as well?" ) else: label = _("Publish copies") help_text = ungettext( "%(count)s of the pages being copied is live. Would you like to publish its copy?", "%(count)s of the pages being copied are live. Would you like to publish their copies?", pages_to_publish_count) % { 'count': pages_to_publish_count } self.fields['publish_copies'] = forms.BooleanField( required=False, initial=True, label=label, help_text=help_text)
def copy(request, page_id): page = Page.objects.get(id=page_id) # Parent page defaults to parent of source page parent_page = page.get_parent() # Check if the user has permission to publish subpages on the parent can_publish = parent_page.permissions_for_user(request.user).can_publish_subpage() # Create the form form = CopyForm(request.POST or None, user=request.user, page=page, can_publish=can_publish) next_url = get_valid_next_url_from_request(request) for fn in hooks.get_hooks('before_copy_page'): result = fn(request, page) if hasattr(result, 'status_code'): return result # Check if user is submitting if request.method == 'POST': # Prefill parent_page in case the form is invalid (as prepopulated value for the form field, # because ModelChoiceField seems to not fall back to the user given value) parent_page = Page.objects.get(id=request.POST['new_parent_page']) if form.is_valid(): # Receive the parent page (this should never be empty) if form.cleaned_data['new_parent_page']: parent_page = form.cleaned_data['new_parent_page'] if not page.permissions_for_user(request.user).can_copy_to( parent_page, form.cleaned_data.get('copy_subpages')): raise PermissionDenied # Re-check if the user has permission to publish subpages on the new parent can_publish = parent_page.permissions_for_user(request.user).can_publish_subpage() # build translated attrs translated_attrs = {} for lang in mt_settings.AVAILABLE_LANGUAGES: new_title_field = build_localized_fieldname("new_title", lang) if form.cleaned_data.get(new_title_field): title_field = build_localized_fieldname("title", lang) new_slug_field = build_localized_fieldname("new_slug", lang) slug_field = build_localized_fieldname("slug", lang) translated_attrs.update({ '{}'.format(title_field): form.cleaned_data[new_title_field], '{}'.format(slug_field): form.cleaned_data[new_slug_field], }) # Copy the page new_page = page.copy( recursive=form.cleaned_data.get('copy_subpages'), to=parent_page, update_attrs=translated_attrs, keep_live=(can_publish and form.cleaned_data.get('publish_copies')), user=request.user, ) # Give a success message back to the user if form.cleaned_data.get('copy_subpages'): messages.success( request, _("Page '{0}' and {1} subpages copied.").format( page.get_admin_display_title(), new_page.get_descendants().count()) ) else: messages.success(request, _("Page '{0}' copied.").format( page.get_admin_display_title())) for fn in hooks.get_hooks('after_copy_page'): result = fn(request, page, new_page) if hasattr(result, 'status_code'): return result # Redirect to explore of parent page if next_url: return redirect(next_url) return redirect('wagtailadmin_explore', parent_page.id) return render(request, 'wagtailadmin/pages/copy.html', { 'page': page, 'form': form, 'next': next_url, })
def append_lang(source): if source in self.trans_opts.fields: return build_localized_fieldname(source, lang) return source
def test_translation_api_behaviour(self): """ NOTE: This test covers for API behaviour for translation mixin serializer TODO: Use model with more then one field used for translation """ country = Country.objects.create(name='country', iso='YY') district = District.objects.create(name='district', country=country) disaster_names = { 'en': 'Disaster 1 (EN)', 'es': 'Disaster 1 (ES)', 'fr': 'Disaster 1 (FR)', 'ar': 'Disaster 1 (AR)', } names = { 'en': 'Project 1 (EN)', 'es': 'Project 1 (ES)', 'fr': 'Project 1 (FR)', 'ar': 'Project 1 (AR)', } disaster_type = DisasterType() for lang, _ in settings.LANGUAGES: setattr(disaster_type, build_localized_fieldname('name', lang), disaster_names[lang]) disaster_type.save() self.authenticate(self.user) # Using both header and GET Param for using in ['header']: for current_language, _ in settings.LANGUAGES: body = { 'reporting_ns': country.id, 'project_country': district.country.id, 'dtype': disaster_type.pk, 'project_districts': [district.id], 'name': names[current_language], 'programme_type': ProgrammeTypes.BILATERAL.value, 'primary_sector': Sectors.WASH.value, 'secondary_sectors': [Sectors.CEA.value, Sectors.PGI.value], 'operation_type': OperationTypes.EMERGENCY_OPERATION.value, 'start_date': '2012-11-12', 'end_date': '2013-11-13', 'budget_amount': 7000, 'target_total': 100, 'status': Statuses.PLANNED.value, } # POST (Creation) with self.capture_on_commit_callbacks(execute=True): # https://docs.djangoproject.com/en/3.0/topics/testing/tools/#setting-the-language resp = self.client.post( '/api/v2/project/', body, HTTP_ACCEPT_LANGUAGE=current_language) # NOTE: non-safe methods are not allowd for non english language if current_language == settings.LANGUAGE_CODE: self.assertEqual(resp.status_code, 201, resp.content) else: self.assertEqual(resp.status_code, 405, resp.content) project_id = resp.json().get('id') if project_id is None: continue # GET for lang, _ in settings.LANGUAGES: resp = self.client.get(f'/api/v2/project/{project_id}/', HTTP_ACCEPT_LANGUAGE=lang) self.assertEqual(resp.status_code, 200, resp.content) resp_body = resp.json() if lang == current_language: assert resp_body['name'] == names[current_language], \ f"Name ({lang}): <{resp_body['name']}> should be <{names[current_language]}>" else: translated_text = self.aws_translator._fake_translation( names[current_language], lang, current_language) assert resp_body['name'] == translated_text,\ f"Name ({lang}): should be <{translated_text}> instead of <{resp_body['name']}>" # Test Nested Field Disaster Type Name assert resp_body['dtype_detail']['name'] == disaster_names[lang], \ f"Name ({lang}): <{resp_body['dtype_detail']['name']}> should be <{disaster_names[lang]}>" # Update (This doesn't reset other language) body['name'] += '' with self.capture_on_commit_callbacks(execute=True): resp = self.client.put( f'/api/v2/project/{project_id}/', body, HTTP_ACCEPT_LANGUAGE=current_language) self.assertEqual(resp.status_code, 200, resp.content) # non current language field should not be None project = Project.objects.get(pk=project_id) for lang, _ in settings.LANGUAGES: value = getattr(project, build_localized_fieldname('name', lang)) if lang == current_language: assert value == body[ 'name'], f"Name ({lang}): <{value}> should be {body['name']}" else: assert value is not None, f"Name ({lang}): value shouldn't be None" # Update (Reset other language) (Without onCommit Trigger by not using self.capture_on_commit_callbacks) # This way the language field are reset but auto translation work is reverted back i.e reset is preserved body['name'] += ' Changed' resp = self.client.put(f'/api/v2/project/{project_id}/', body, HTTP_ACCEPT_LANGUAGE=current_language) self.assertEqual(resp.status_code, 200, resp.content) # non current language field should be None project = Project.objects.get(pk=project_id) for lang, _ in settings.LANGUAGES: value = getattr(project, build_localized_fieldname('name', lang)) if lang == current_language: assert value == body[ 'name'], f"Name ({lang}): <{value}> should be {body['name']}" else: assert value is None, f"Name ({lang}): <{value}> should be None" # Again Update (With onCommit Trigger: Mock Translation) body['name'] += ' Again Changed' with self.capture_on_commit_callbacks(execute=True): resp = self.client.put( f'/api/v2/project/{project_id}/', body, HTTP_ACCEPT_LANGUAGE=current_language) self.assertEqual(resp.status_code, 200, resp.content) # non current language field should be None project = Project.objects.get(pk=project_id) for lang, _ in settings.LANGUAGES: value = getattr(project, build_localized_fieldname('name', lang)) if lang == current_language: assert value == body[ 'name'], f"Name ({lang}): <{value}> should be {body['name']}" else: assert value is not None, f"Name ({lang}): <{value}> should not be None"