Added jquery-ui based admin support for tabbed translation fields. Resolves issue 39 (thanks to jaap and adamsc).

This commit is contained in:
Dirk Eschler 2010-08-05 19:38:05 +00:00
parent 704a559c0d
commit 6438ef28e5
5 changed files with 213 additions and 5 deletions

View file

@ -1,3 +1,5 @@
ADDED: Jquery-ui based admin support for tabbed translation fields.
(thanks to jaap and adamsc, resolves issue 39)
ADDED: CSS class to identify a translation field and the default translation
field in admin.
(thanks to jaap)

View file

@ -57,6 +57,7 @@ in detail in the following sections:
synced to the database before. If they did - read further down what to do
in that case.
Configure the project's ``settings.py``
---------------------------------------
The following variables have to be added to or edited in the project's
@ -250,6 +251,7 @@ The example above assumes that the default language is ``de``, therefore the
fields*. If the default language is ``en``, the ``title_en`` and ``text_en``
fields would be the *default translation fields*.
Rules for translated field access
---------------------------------
So now when it comes to setting and getting the value of the original and the
@ -301,6 +303,12 @@ the ``django.utils.i18n.get_language`` function to determine the current
language.
Set a default value
-------------------
*New in development version*
TODO
Django admin backend integration
================================
In order to be able to edit the translations via the admin backend you need to
@ -316,9 +324,9 @@ patching on all your models registered for translation::
admin.site.register(News, NewsAdmin)
Tweaks applied to the admin
---------------------------
The ``TranslationAdmin`` class does only implement one special method which is
``def formfield_for_dbfield(self, db_field, **kwargs)``. This method does the
following:
@ -329,6 +337,7 @@ following:
3. Checks if the - now removed - original field was required and if so makes the
default translation field required instead.
TranslationAdmin in combination with other admin classes
--------------------------------------------------------
If there already exists a custom admin class for a translated model and you
@ -433,10 +442,32 @@ class TranslatedNewsAdmin(NewsAdmin, TranslationAdmin):
admin.site.register(News, NewsAdmin)
}}}
Set a default value
-------------------
*New in developement version*
TODO
Using tabbed translation fields
-------------------------------
*New in development version*
Modeltranslation supports separation of translation fields via jquery-ui tabs.
The proposed way to include it is through the inner `Media` class of a
`TranslationAdmin` class like this:
{{{
class NewsAdmin(TranslationAdmin):
class Media:
js = (
'/static/modeltranslation/js/force_jquery.js',
'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js',
'/static/modeltranslation/js/tabbed_translation_fields.js',
)
css = {
'screen': ('/static/modeltranslation/js/tabbed_translation_fields.css',),
}
}}}
The `force_jquery.js` script is necessary when using Django's built-in
`django.jQuery` object. This and the static urls used are just an example and
might have to be adopted to your setup of serving static files. Standard
jquery-ui theming can be used to customize the look of tabs, the provided css
file is supposed to work well with a default Django admin.
The ``update_translation_fields`` command
@ -488,6 +519,7 @@ field is updated::
'foo'
>>>
Accessing translated fields outside views
-----------------------------------------
Since the ``modeltranslation`` mechanism relies on the current language as it
@ -529,6 +561,7 @@ It is not possible to reuse existing models without modifying them.
A much simpler version of the above `django-multilingual`.
It works very similiar to the `django-multilingual` approach.
`transdb`_
----------
@ -537,6 +570,7 @@ It works very similiar to the `django-multilingual` approach.
This approach uses a specialized ``Field`` class, which means one has to change
existing models.
`i18ndynamic`_
--------------
This approach is not developed any more.

View file

@ -0,0 +1,66 @@
/*
* jQuery UI CSS Framework
* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
* http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/base/jquery.ui.core.css
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
.ui-helper-clearfix { display: inline-block; }
/* required comment for clearfix to work in Opera \*/
* html .ui-helper-clearfix { height:1%; }
.ui-helper-clearfix { display:block; }
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
.ui-state-disabled { cursor: default !important; }
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
/* http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/base/jquery.ui.tabs.css */
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
.ui-tabs .ui-tabs-hide {
position: absolute;
left: -10000px;
}
/* custom tabs theme */
.ui-tabs { padding: 0; }
.ui-tabs .ui-tabs-nav { padding: 5px 0 0 10px; border-bottom: 1px solid #EEEEEE; }
.ui-tabs .ui-tabs-nav li { margin: 0; }
.ui-tabs .ui-tabs-nav li.required { font-weight: bold; }
.ui-tabs .ui-tabs-nav li a {
border: 1px solid #CCCCCC;
background: #eeeeee repeat-x;
border-bottom-width: 0;
color: #666666;
padding: 4px 10px 4px 10px;
margin-top: 2px;
-moz-border-radius-topright: 4px;
-webkit-border-top-right-radius: 4px;
-moz-border-radius-topleft: 4px;
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
font-size: 12px;
}
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a{
background: #7CA0C7 repeat-x;
color: #fff;
padding: 6px 10px 4px 10px;
margin-top: 0;
}
.ui-tabs .ui-tabs-panel{
padding: 0;
}

View file

@ -0,0 +1,3 @@
if (!jQuery) {
jQuery = django.jQuery;
}

View file

@ -0,0 +1,103 @@
/*jslint white: true, onevar: true, undef: true, nomen: true, eqeqeq: true,
plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true */
var google, django, gettext;
(function () {
var jQuery = django.jQuery || jQuery || $;
jQuery(function ($) {
function getGroupedTranslationFields() {
/** Returns a grouped set of all text based model translation fields.
* The returned datastructure will look something like this:
* {
* 'title': {
* 'en': HTMLInputElement,
* 'de': HTMLInputElement,
* 'fr': HTMLInputElement
* },
* 'body': {
* 'en': HTMLTextAreaElement,
* 'de': HTMLTextAreaElement,
* 'fr': HTMLTextAreaElement
* }
* }
*/
var translation_fields = $('.modeltranslation').filter('input[type=text]:visible, textarea:visible'),
grouped_translations = {};
translation_fields.each(function (i, el) {
var name = $(el).attr('name').split('_'),
lang = name.pop();
name = name.join('_');
if (!grouped_translations[name]) {
grouped_translations[name] = {};
}
grouped_translations[name][lang] = el;
});
return grouped_translations;
}
function createTabs() {
var grouped_translations = getGroupedTranslationFields();
var tabs = [];
$.each(grouped_translations, function (name, languages) {
var tabs_container = $('<div></div>'),
tabs_list = $('<ul></ul>'),
insertion_point;
tabs_container.append(tabs_list);
$.each(languages, function (lang, el) {
var container = $(el).closest('.form-row'),
label = $('label', container),
field_label = container.find('label'),
id = 'tab_' + [name, lang].join('_'),
panel, tab;
// Remove language and brackets from field label, they are
// displayed in the tab already.
if (field_label.html()) {
field_label.html(field_label.html().replace(/\ \[.+\]/, ''));
}
if (!insertion_point) {
insertion_point = {
'insert': container.prev().length ? 'after' : container.next().length ? 'prepend' : 'append',
'el': container.prev().length ? container.prev() : container.parent()
};
}
panel = $('<div id="' + id + '"></div>').append(container);
tab = $('<li' + (label.hasClass('required') ? ' class="required"' : '') + '><a href="#' + id + '">' + lang + '</a></li>');
tabs_list.append(tab);
tabs_container.append(panel);
});
insertion_point.el[insertion_point.insert](tabs_container);
tabs_container.tabs();
tabs.push(tabs_container);
});
return tabs;
}
function createMainSwitch(tabs) {
var grouped_translations = getGroupedTranslationFields(),
unique_languages = [],
select = $('<select>');
$.each(grouped_translations, function (name, languages) {
$.each(languages, function (lang, el) {
if ($.inArray(lang, unique_languages) < 0) {
unique_languages.push(lang);
}
});
});
$.each(unique_languages, function (i, language) {
select.append($('<option value="' + i + '">' + language + '</option>'));
});
select.change(function (e) {
$.each(tabs, function (i, tab) {
tab.tabs('select', parseInt(select.val()));
});
});
$('#content h1').append('&nbsp;').append(select);
}
if ($('body').hasClass('change-form')) {
createMainSwitch(createTabs());
}
});
}());