diff --git a/apps/addons/models.py b/apps/addons/models.py index e82e5e2656b..eed3b767404 100644 --- a/apps/addons/models.py +++ b/apps/addons/models.py @@ -116,7 +116,7 @@ class Addon(amo.models.ModelBase): homepage = TranslatedField() support_email = TranslatedField(db_column='supportemail') support_url = TranslatedField(db_column='supporturl') - description = PurifiedField() + description = PurifiedField(short=False) summary = LinkifiedField() developer_comments = PurifiedField(db_column='developercomments') diff --git a/apps/translations/fields.py b/apps/translations/fields.py index 66e27a7d252..5f58b91b4be 100644 --- a/apps/translations/fields.py +++ b/apps/translations/fields.py @@ -6,7 +6,7 @@ from django.utils.translation.trans_real import to_language from .models import Translation, PurifiedTranslation, LinkifiedTranslation -from .widgets import TranslationWidget +from .widgets import TransInput, TransTextarea class TranslatedField(models.ForeignKey): @@ -24,6 +24,7 @@ def __init__(self, **kwargs): # Django wants to default to translations.autoid, but we need id. options = dict(null=True, to_field='id', unique=True, blank=True) kwargs.update(options) + self.short = kwargs.pop('short', True) self.require_locale = kwargs.pop('require_locale', True) super(TranslatedField, self).__init__(self.to, **kwargs) @@ -57,7 +58,8 @@ def contribute_to_class(self, cls, name): setattr(cls, self.name, TranslationDescriptor(self)) def formfield(self, **kw): - defaults = {'form_class': TranslationFormField} + widget = TransInput if self.short else TransTextarea + defaults = {'form_class': TranslationFormField, 'widget': widget} defaults.update(kw) return super(TranslatedField, self).formfield(**defaults) @@ -177,12 +179,11 @@ def translation_from_dict(self, instance, lang, dict_): class TranslationFormField(forms.Field): - widget = TranslationWidget - def __init__(self, *args, **kwargs): for k in ('queryset', 'to_field_name'): if k in kwargs: del kwargs[k] + self.widget = kwargs.pop('widget', TransInput) super(TranslationFormField, self).__init__(*args, **kwargs) def clean(self, value): diff --git a/apps/translations/tests/test_models.py b/apps/translations/tests/test_models.py index 9986f6abb99..c259207135d 100644 --- a/apps/translations/tests/test_models.py +++ b/apps/translations/tests/test_models.py @@ -208,16 +208,6 @@ def test_dict_bad_locale(self): eq_(sorted(ts.values_list('locale', flat=True)), ['de', 'en-US', 'es-ES']) - def test_widget(self): - strings = {'de': None, 'fr': 'oui'} - o = TranslatedModel.objects.get(id=1) - o.name = strings - o.save() - - # Shouldn't see de since that's NULL now. - ws = widgets.trans_widgets(o.name_id, lambda *args: None) - eq_(sorted(dict(ws).keys()), ['en-us', 'fr']) - def test_sorting(self): """Test translation comparisons in Python code.""" b = Translation.new('bbbb', 'de') @@ -363,7 +353,7 @@ def test_translation_unicode(): def test_widget_value_from_datadict(): data = {'f_en-US': 'woo', 'f_de': 'herr', 'f_fr_delete': ''} - actual = widgets.TranslationWidget().value_from_datadict(data, [], 'f') + actual = widgets.TransMulti().value_from_datadict(data, [], 'f') expected = {'en-US': 'woo', 'de': 'herr', 'fr': None} eq_(actual, expected) diff --git a/apps/translations/widgets.py b/apps/translations/widgets.py index 7ae67b014cd..80446e07298 100644 --- a/apps/translations/widgets.py +++ b/apps/translations/widgets.py @@ -1,21 +1,10 @@ from django import forms -from django.conf import settings -from django.forms.util import flatatt from django.utils import translation from django.utils.translation.trans_real import to_language -import jinja2 - -import jingo - from .models import Translation -attrs = 'name="{name}_{locale}" data-locale="{locale}" {attrs}' -input = u'' % attrs -textarea = u'' % attrs - - def get_string(x): locale = translation.get_language() try: @@ -43,32 +32,41 @@ def render(self, name, value, attrs=None): return super(TranslationTextarea, self).render(name, value, attrs) -class TranslationWidget(forms.widgets.Textarea): - - # Django expects ForeignKey widgets to have a choices attribute. - choices = None - - def render(self, name, value, attrs=None): - - attrs = self.build_attrs(attrs) - widget = widget_builder(name, attrs) - id = attrs.pop('id') - - lang = translation.get_language() - widgets = {} - widgets[lang] = widget(lang, value='') +class TransMulti(forms.widgets.MultiWidget): + """ + Builds the inputs for a translatable field. - try: - trans_id = int(value) - widgets.update(trans_widgets(trans_id, widget)) - except (TypeError, ValueError): - pass + The backend dumps all the available translations into a set of widgets + wrapped in div.trans and javascript handles the rest of the UI. + """ - languages = dict((i.lower(), j) for i, j in settings.LANGUAGES.items()) + def __init__(self): + # We set up the widgets in render since every Translation needs a + # different number of widgets. + super(TransMulti, self).__init__(widgets=[]) - template = jingo.env.get_template('translations/transbox.html') - return template.render(id=id, name=name, widgets=widgets, - languages=languages) + def render(self, name, value, attrs=None): + self.name = name + value = self.decompress(value) + if value: + self.widgets = [self.widget() for _ in value] + else: + # Give an empty widget in the current locale. + self.widgets = [self.widget()] + value = [Translation(locale=translation.get_language())] + return super(TransMulti, self).render(name, value, attrs) + + def decompress(self, value): + if not value: + return [] + elif isinstance(value, long): + # We got a foreign key to the translation table. + qs = Translation.objects.filter(id=value) + return list(qs.filter(localized_string__isnull=False)) + elif isinstance(value, dict): + # We're getting a datadict, there was a validation error. + return [Translation(locale=k, localized_string=v) + for k, v in value.items()] def value_from_datadict(self, data, files, name): # All the translations for this field are called {name}_{locale}, so @@ -85,21 +83,31 @@ def value_from_datadict(self, data, files, name): rv[locale(key)] = data[key] return rv + def format_output(self, widgets): + s = super(TransMulti, self).format_output(widgets) + return '
%s
' % (self.name, s) + + +class _TransWidget(object): + """ + Widget mixin that adds a Translation locale to the lang attribute and the + input name. + """ + + def render(self, name, value, attrs=None): + attrs = attrs or {} + lang = to_language(value.locale) + attrs.update(lang=lang) + # Use rsplit to drop django's name_idx numbering. (name_0 => name) + name = '%s_%s' % (name.rsplit('_', 1)[0], lang) + return super(_TransWidget, self).render(name, value, attrs) -def trans_widgets(trans_id, widget): - translations = (Translation.objects.filter(id=trans_id) - .filter(localized_string__isnull=False) - .values_list('locale', 'localized_string')) - return [(to_language(locale), widget(locale, val)) - for locale, val in translations if val is not None] +# TransInput and TransTextarea are MultiWidgets that know how to set up our +# special translation attributes. +class TransInput(TransMulti): + widget = type('_TextInput', (_TransWidget, forms.widgets.TextInput), {}) -def widget_builder(name, attrs): - def widget(locale, value): - locale = to_language(locale) - value = jinja2.escape(value) - attrs_ = dict(id='trans_%s_%s' % (name, locale), **attrs) - return textarea.format(name=name, locale=locale, - attrs=flatatt(attrs_), value=value) - return widget +class TransTextarea(TransMulti): + widget = type('_Textarea', (_TransWidget, forms.widgets.Textarea), {})