Административная панель Django очень удобна, но в ней не хватает хорошего редактора HTML, иными словами WYSIWYG
В поиске есть инструкция по интеграции таких редакторов как tinymce или wymeditor, но первый очень громоздкий, а второй сбоит и глючит при вводе текста.
После сравнения редакторов был выбран http://ckeditor.com/, ключевым фактором моего выбора была чистота кода, удобство настройки и русская версия.
В статье я покажу как его интегрировать в Django
Подключаем новый тип поля
from sites.common.objects import TextField
Тип поля в моделе будет выглядеть так:
tag_full_descr = TextField.field( u"Полное описание", max_length=1024, null=True, blank=True)
Для возможности загрузки картинок на сервер добавьте в urls.py
from sites.common.objects import TextField
добавлям в обработчик урлов( файл urls.py)
,url(r'^uploader/admin_upload/$',TextField.upload)
также нужно создать файл '/js/ckeditor_init.js', он нужен для коректной работы редактора в InlineModelAdmin objects, и для инициализации редактора.
Файл ckeditor_init.js
$(document).ready(function() { $('.inline-related').live('DOMNodeInserted', function(){ var textarea = $(this).find('textarea[ckeditor=ckeditor]' ) enable_ckeditor( textarea ) }) function enable_ckeditor( obj ){ obj.ckeditor({ skin : 'v2' //,startupMode : 'source' ,startupOutlineBlocks : true ,filebrowserUploadUrl : '/uploader/admin_upload/?ref='+window.location.pathname ,toolbar : [ { name: 'document', items : [ 'Maximize', 'Source','DocProps','Preview','RemoveFormat','-','Templates' ] }, { name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] }, { name: 'links', items : [ 'Link','Unlink','Image','Table','HorizontalRule' ] }, '/', { name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript'] }, { name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] }, { name: 'colors', items : [ 'TextColor','BGColor' ] }, { name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote', '-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] }, ] }) } $( 'textarea[ckeditor=ckeditor]' ).each( function(index){ var obj = $(this) if (obj.attr('id').indexOf('__prefix__') == -1){ enable_ckeditor( obj ) } }) })
Файл TextField.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
from django import forms
from django.utils.safestring import mark_safe
from django.contrib.admin.widgets import AdminTextareaWidget
from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS
from django.http import HttpResponse
from text import text ### используется функция translify, для транслитерации русских букв
import commonFunction ### используется getExceptionError(), для получения текста исключения
import commonException ### используется класс ошибок, вы можете использовать свой
from django.conf import settings
from django.db import models
from sites.common import ImageWorkshop ###используется 2 функции из библиотеки PIL( open и save )
import os
class field(models.TextField):
pass
def upload( request, **kargs ):
object_upload = request.GET['ref'].split('/')[-3] ### обычно в админке путь похожий на http://foothold.ru/admin/article_menu/article/23/, мы берем article, чтоб имя изображения боле более читабельным
try:
file_to_upload = request.FILES.get('upload')
if file_to_upload == None:
raise e_no_file
try:
valid_image_types = settings._valid_image_types #### с настройках можно указать валидные типы для загрузки
except AttributeError:
valid_image_types = ['image/png', 'image/gif', 'image/jpg', 'image/jpeg', 'image/pjpeg', 'image/x-png']
if file_to_upload.content_type not in valid_image_types:
raise e_bad_content_type( type = file_to_upload.content_type )
original_path = "{0}/images/{1}".format(settings.DOCUMENT_ROOT,object_upload)
if not os.path.exists( original_path ):
os.makedirs( original_path )
file_name_wo_ext, extension = os.path.splitext( file_to_upload.name )
file_name_wo_ext = text.translify( file_name_wo_ext )
extension = text.translify( extension )
index = 0
while True: #### если такой файл уже существует мы к имени добавим индекс
if index > 0:
file_name = "{0}-{1}.{2}".format(file_name_wo_ext, index, extension.strip('.'))
else:
file_name = "{0}.{1}".format(file_name_wo_ext, extension.strip('.'))
index = index + 1
full_file_name = original_path + '/' + file_name
url_file_name = "/images/{0}/{1}".format(object_upload,file_name)
if not os.path.exists( full_file_name ):
break
image_file = ImageWorkshop.open(file_to_upload)
image_file = ImageWorkshop.scale_and_crop( image_file, ( 1000, 9000 ), {} ) ##### уменьшаем картинку чтоб пользователи не грузили большие файлы
ImageWorkshop.save( image_file, full_file_name, quality=90)
result ="""""".format(request.GET['CKEditorFuncNum'], url_file_name )
except:
error = commonFunction.getExceptionError()
result ="""""".format(request.GET['CKEditorFuncNum'], '',error.replace('\n', ' ') )
return HttpResponse(result)
class upload_exception( commonException.exception):
pass
class e_no_file( upload_exception ):
_message = u"Нет файла для загрузки"
class e_bad_content_type( upload_exception ):
_message = u"Тип файлов {type} не могут быть загружен"
class textFieldTextarea(forms.Textarea):
def render(self, name, value, attrs=None):
#print self
#print type(name), type(value)
if value is not None:
try:
value = value.raw
except AttributeError:
pass
return super(textFieldTextarea, self).render(name, value, attrs)
class textFieldWidget(textFieldTextarea):
def _media(self):
return forms.Media(
js=( '/js/jquery.min.js'
,'/js/ckeditor/ckeditor.js'
,'/js/ckeditor/adapters/jquery.js'
,'/js/ckeditor_init.js'
)
)
media = property(_media)
def render(self, name, value, attrs=None):
attrs['ckeditor'] = "ckeditor"
html = super(textFieldWidget, self).render(name, value, attrs)
return mark_safe(html)
class AdmintextFieldWidget(textFieldWidget, AdminTextareaWidget):
pass
FORMFIELD_FOR_DBFIELD_DEFAULTS[field] = {'widget': AdmintextFieldWidget}