Допиливаем Django-admin бензопилой. Часть II — WYSIWYG

Итак обещанное продолжение прошлого поста. В результате мы прикрутим TinyMCE к flatpages и превратим лист/форму созданной модели с картинками в простой файловый менеджер для вставки картинок. Для этого понадобится совсем немного Javascript и единственная строчка в 'admin.py'.

Во-первых хочу сразу оговориться, что доволен тем фактом, что разделил эту статью на две части — появилась возможность почитать комментарии (на хабре), учесть некоторые ошибки и на основе этого сразу сделать небольшое предупреждение. Если вы просто хотите прикрутить thumbnails или WYSIWYG-редактор в ваше Django-приложение, то эта статья не для вас: возьмите готовый плагин и не парьтесь — наверняка получите более мощное и функциональное решение. Хотя для каждого простого случая добавлять зависимости сомнительного происхождения (аля djangosnippets) в проект — тоже не вариант.

Моя цель состоит в другом — на простых примерах показать как с помощью кастомного JS можно безболезненно решать подобные задачи и другие, намного более сложные, решение которых «в лоб» возможно потребовало бы грязных низкоуровневых хаков в коде Django.

Итак, в результате первой части у нас имеется модель для дополнительных картинок, которые мы намерены использовать для flatpages, и настроенную админку, которая теперь умеет отображать thumbnails картинок в списке объектов и на форме редактирования.



Для начала подключим TinyMCE. Предпочитаю именно этот редактор — дело вкуса и привычки. Тем более, что сейчас вместе с ним поставляется плагин для jQuery, там что довольно неплохо вписывается в набор используемых мною JS библиотек. Сам этот плагин вместе с файликом 'base.js' мы уже подключили в прошлой части глобально, добавив его в 'admin/base_site.html', что довольно удобно в случае, если WYSIWYG вам нужен для нескольких моделей. Код инициализации и настройки TinyMCE будет находиться в 'base.js'. Приведём его к следующему виду:

function tinyDjangoBrowser(field_name, url, type, win) {
    var managerURL = window.location.toString()
            + '../../../shop/pagepicture/?type=' + type;

    tinyMCE.activeEditor.windowManager.open({
        file: managerURL,
        title: 'Кликните на эскиз нужной картинки',
        width: 800,
        height: 450,
        resizable: 'yes',
        inline: 'yes',
        close_previous: 'no',
        popup_css : false
    }, {
        window: win,
        input: field_name
    });

    return false;
}

$().ready(function() {
    var tinymceOptions = {
        script_url: '/media/lib/tiny_mce/tiny_mce.js',

        theme: 'advanced',
        plugins: 'safari,pagebreak,layer,table,advhr,advimage,advlink,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras',

        theme_advanced_buttons1: 'bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,formatselect,fontselect,fontsizeselect',
        theme_advanced_buttons2: 'search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor',
        theme_advanced_buttons3: 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,iespell,media,advhr,|,print,|,fullscreen',
        theme_advanced_buttons4: 'insertlayer,moveforward,movebackward,absolute,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,pagebreak',
        theme_advanced_toolbar_location: 'top',
        theme_advanced_toolbar_align: 'left',
        theme_advanced_statusbar_location: 'bottom',
        theme_advanced_resizing: true,

        // Drop lists for link/image/media/template dialogs
        template_external_list_url: 'lists/template_list.js',
        external_link_list_url: 'lists/link_list.js',
        external_image_list_url: 'lists/image_list.js',
        media_external_list_url: 'lists/media_list.js',

        doctype: '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
        content_css: '/media/css/admin/editor.css',

        file_browser_callback: 'tinyDjangoBrowser'
    };

    $('textarea#id_content').tinymce(tinymceOptions);
});


Вот собственно и всё. Теперь на форме редактирования flatpages у нас будет TinyMCE в почти максимальной конфигурации. В том числе с возможностью вставлять изображения и редактировать их свойства.



Однако вставлять картинки по их URL это пол-дела, необходимо обеспечить возможность прямо из диалога вставки картинки, без лишних телодвижений залить на сервер новую картинку, просмотреть имеющиеся и выбрать нужную. Именно такую функциональность обеспецивают File/Image managers для популярных WYSIWYG редакторов. Их отличительной особенностью является то, что для работы им необходима серверная часть — так называемый «коннектор», который позволяет осуществлять необходимые действия с файловой системой, а менеджеру останется лишь отображать необходимую информацию и возвращать параметры выбранной картинки диалоговому окну.



Вобщем-то всю эту функциональность нам уже предоставляет сама Django, точнее наша готовая модель для управления картинками. Остаётся только заставить TinyMCE использовать её же в качестве менеджера файлов. Для этого нам и нужна функция 'tinyDjangoBrowser()' и опция TinyMCE 'file_browser_callback'. Всё что делает эта функция — открывает страницу управления моделью с картинками в popup-окне. Этого уже достаточно для того чтобы выбрать/удалить/закачать картинки. Дело остаётся за малым — страница со списком объетов должна различать была ли она открыта просто так или из TinyMCE-окна, и в последнем случае возвращать параметры выбранной картинки. Для этого декорируем админку для этой модели c помощью небольшого скрипта 'tiny_django_browser.js'.

$().ready(function() {
    if (window.parent) {
        $.getScript('/media/lib/tiny_mce/tiny_mce_popup.js', function() {
            $('#changelist tbody a.image-picker').click(function(e) {
                var url = $(this).attr('href');
                var win = tinyMCEPopup.getWindowArg("window");
                var alt = $(this).find('img').attr('alt');

                win.document.getElementById(tinyMCEPopup.getWindowArg("input")).value = url;
                win.document.getElementById('alt').value = alt;

                if (typeof(win.ImageDialog) != "undefined") {
                    if (win.ImageDialog.getImageData) win.ImageDialog.getImageData();
                    if (win.ImageDialog.showPreviewImage) win.ImageDialog.showPreviewImage(url);
                }

                tinyMCEPopup.close();
                return false;
            });
        });
    }
});


Дело за малым, подключаем этот скрипт к админке, FlatPictureAdmin на данный момент будет выглядеть следующим образом:

class PagePictureAdmin(admin.ModelAdmin):
    class Media:
        js = ['js/admin/display_thumbs.js', 'js/admin/tiny_django_browser.js']
    list_display = ['get_thumbnail_html', '__unicode__', 'description']
    list_display_links = ['__unicode__']
admin.site.register(PagePicture, PagePictureAdmin)


Вот и всё. Теперь картинка вместе с описанием в alt-аттрибуте будет вставляться по клику на thumbnail.



В следующий раз постараюсь подобрать пример более сложной и редкой функциональности. Надеюсь, что удастся почерпнуть какие-то идеи и предложения из ваших комментариев.
+25
16 декабря 2009, 17:40
54
Frenzy 28,0 G+

комментарии (31)

+1
angry_elf #
Вы молодец, вы прикрутили tinymce к джанго. Теперь, пожалуйста, открутите его обратно.

Не знаю как кто, но я не видел еще ни одного рабочего примера, когда tinymce действительно помогал и не являлся источником тысяч поломок вёрстки. Дело, разумеется, не в tinymce, а в копипастах из ворда, которое так любят делать владельцы сайта.
+3
Zigzag #
Вы предлагаете научить каждую Машу-секретаршу, на которую повесили сайт овладеть HTML или bbCode, или может еще Markdown?

работать с заказчиком надо, постоянно вбивать ему в голову, что копипаст из Ворда это не кошерно и рано или поздно, он сдастся.
0
angry_elf #
Так если уж вбивать что-то заказчику в голову, так сразу мысль о том, что для работы с сайтом нужен специально обученный человек?

С машей-секретаршей вопрос решается тривиально — ей даётся механизм вбивания контента предопределённого формата, не более. Тут заголовок, тут краткий текст, тут полный текст, тут картинки к тексту (преобразуме на сервере под предопределённый размер), тут аттач загружаем. Всё, хватит. Что-то большее должен делать специалист и точка.
0
Frenzy #
у меня подруга админит интернет-магазин. пишет большие обзоры товаров с картинками, разными цветами, видео и т.д. — никакого предопределённого механизма. как вы предлагаете объяснить человеку, что ей нужно учить HTML, если она прекрасно знает что в «других CMS» есть WYSIWYG редактор, соответственно не понимает почему она должна париться, если у нас тут xxi век и супер-пупер технологии
–1
angry_elf #
У меня в практике было слишком много плохих примеров использования вижуфиг-редакторов, может поэтому я такой резкий?

Если ваша подруга ни разу не ломала ни верстку, ни общий стиль сайта (о, как любят маши-секретарши цветной подчеркнутый курсив h1), то я могу сказать только одно — она — специально обученный человек :)
0
Zigzag #
Это отдельный случай. По уму так и должно быть, отдельная штатная единица для работы с сайтом, но на практике мало кто так делает.

Правильнее будет в том же TinyMCE запихнуть шаблоны страниц.
+2
kmike #
Маша-секретарша сама научится, поставить bitbucket.org/carljm/django-markitup/overview/ да и все. Удобная штука.
0
Chikiro #
У меня заказчик попросил открутить markitup и прикрутить tinymce :(
–2
egorinsk #
Я бы предложил для каждого сайта делать свой хороший редактор, а не тупо втыкать скачанный откуда-то бесплатный редакктор с сотней мелких неудобных кнопок. Но ведь никто не согласится!
0
Zigzag #
А еще свою уникальную CMS для каждого сайта! Ага.

Ну честное слово, мы же живем в реальном мире, ну какой нафиг уникальный редактор? Ведь лучшие opensource щчень хорошо настраиваются. Все лишние кнопки легко убираются.
0
sapegin #
Дык что мешает в готовом редакторе выключить лишние кнопки и настроить стили?
0
egorinsk #
Сущность то у него та же останется, пакостная. TINYMCE рассчитан на людей, представляющих что такое HTML — и никак иначе.
0
AlienZzzz #
я научил ) Markdown но сейчас ищу замену) так как там есть до поля ((( не кашерно
+2
el777 #
По хорошему, там должна быть кнопочка «Очистить вордо-мусор».
К сожалению, не всегда помогает.

Поэтому в нашем XXI веке лучший способ скопипастить из ворда — это через блокнот. Вначале убрать все форматирование, а потом сделать его как надо.
0
Zigzag #
именно так я и прошу делать всегда =)
+1
angry_elf #
В 21-м веке специальную работу делают специалисты.
+1
Nikita #
В текущей редакции tinyMce есть стд. плагин paste, который автоматически очищает оформление при вставке из ворда. В том числе по CTRL+C, CTRL+V. Плагин был тотально переработан в версии 3.2.3 и работает вполне неплохо, хотя имеет некоторые проблемы в webkit.
0
angry_elf #
Хорошо, если работает. Когда я последний раз с ним воевал, чистило оно слабо.
+1
alexeydg #
прикрутите ckeditor, по фционалу он такой же, имеет фцию очистки от ворда + при копировании из ворда иногда определяет, что текст из ворда и предлагает его почистить.
–2
angry_elf #
Те 4 с половиной тэга, которых более чем достаточно для написания 99% контента на среднем сайте, не проблема выучить. Надобность в вижуфиг-редакторах надумана.
+1
alexeydg #
скорее всего у вас маленький опыт работы с клиентами, бывают абсолютно деревянные люди, которым проще что то визуальное, нежели запоминать 4 тега. когда им говоришь вот есть в редакторе кнопочка чтобы посмотреть html-код, от этих слов у них начинает дымиться голова
0
angry_elf #
Еще раз говорю, специальной работой должен заниматься специалист, а не деревянные люди. А что б обучить специалиста, ему надо знать 4 с половиной тэга.

Т.е. не надо искать буратино и давать ему доступ в админку. Найдите вменяемого человека, обучите его (сюрприз, этап обучения в промышленности есть всегда, в отличии от IT) четырём тэгам и забудьте про проблемы.
+1
alexeydg #
есть на свете мелкий бизнес, где наполнением сайта может заниматься даже генеральный, у которого куча дел кроме, чтобы еще учить хтмл. ему проще вбить текст в редактор и забыть.
–1
angry_elf #
Генеральный может потрать 20 баксов в месяц на вебмастера и не отвлекаться от кучи дел.
+1
alexeydg #
ключевое слово «мелкий» бизнес
0
angry_elf #
Такой мелкий бизнес пусть учит html-тэги. Жалко 20 баксов в месяц на специалиста, который вам будет наполнять сайт — смешно.
0
Frenzy #
ну это вы будете заказчику доказывать, когда он вам в SRS напишет «хачу визивиг», а не нам вот здесь :)
0
angry_elf #
В договоре есть пункты типа
1. Обучение (и его стоимость).
2. Ответственность сторон (вам тупая секретарша сломала сайт — платите по прейскуранту. Мы обучали специалиста, где он?).

Пункта «хачу визивиг» там не будет. Честно.
0
Webchemist #
+2
ur001 #
Не знаю, насколько это велосипед, но для подключения редактора (использую wymeditor) такой трюк:

Copy Source | Copy HTML
  1. class WysiwygAdmin(admin.ModelAdmin):
  2.     class Meta:
  3.         # Список полей для которых нужно включить визуальный редактор    
  4.         wysiwyg_fields = ()
  5.  
  6.     # Добавляем класс wysiwyg для всех полей перечисленных в Meta.wysiwyg_fields
  7.     def formfield_for_dbfield(self, db_field, **kwargs):
  8.         field = super(WysiwygAdmin, self).formfield_for_dbfield(db_field, **kwargs)
  9.         if db_field.name in self.Meta.wysiwyg_fields:
  10.             field.widget.attrs['class'] = 'wysiwyg ' + field.widget.attrs.get('class', '')
  11.         return field
  12.  
  13.     # Подключение js/css
  14.     class Media:
  15.         js = ('http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js',
  16.               settings.MEDIA_URL + 'js/wymeditor/jquery.wymeditor.pack.js',
  17.               settings.MEDIA_URL + 'js/wysiwyg.js',)
  18.  
  19.         css = {'screen': (settings.MEDIA_URL + 'js/wymeditor/skins/default/screen.css',)}


wysiwyg.js подключает wymeditor ко всем полям с классом wysiwyg

Copy Source | Copy HTML
$(document).ready(function() {
    $(".wysiwyg").wymeditor({
        updateSelector: "input:submit",
        updateEvent: "click",
        lang: 'ru',
    });
});


После этого достоточно класс для админки наследовать не от admin.ModelAdmin, а от WysiwygAdmin. А дальше 2 варианта: либо определить класс wysiwyg для поля стандартным способом, либо перечислить его имя в Meta. Последний вариант подходит для включения редактора в flatpages:

Copy Source | Copy HTML
class WysiwygFlatPageAdmin(FlatPageAdmin, WysiwygAdmin):
    class Meta:
        wysiwyg_fields = ('content')
 
admin.site.unregister(FlatPage)
admin.site.register(FlatPage, WysiwygFlatPageAdmin)


P.S. Я в Django ещё не профи, по этому с удовольствием приму аргументированную критику :)
0
hazg #
По поводу визивига и ломаной верстки:

Сколько плагины для очистки вордового шлака не воевали с пользователями, последние всегда побеждали. Поэтому server-side tidy после поста — рулит. Другой альтернативы я не нашел.

Это было бы здорово, если умный и опытный человек мне бы написал про новый универсальный способ очистки, не требующий tidy на сервере.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.