При стандартных настройках загрузка картинки не доступна. Редактор предлагает вставить внешний источник. Будь то это сторонний сток или полный путь до картинки на вашем сайте.
Как решить эту проблему.
Изначально скрипт вызова редактора находится app/main/static/django_tinymce/init_tinymce.js. У вас это может быть другой путь. Важно что в файле init_tinymce.js идет вызов редактора и сбор настроек из settings.py.
В моем случае конфиг выглядел так. Есть нюанс — в джанго конфиге нет возможности прописать коллбек-функции для реализации дополнительной обработки.
TINYMCE_DEFAULT_CONFIG = {
"height": "320px",
"width": "960px",
"menubar": "file edit view insert format tools table help",
"plugins": "media image preview advlist autolink lists link charmap print preview anchor searchreplace visualblocks code "
"fullscreen insertdatetime media table paste code help wordcount spellchecker",
"toolbar": "undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft "
"aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | forecolor "
"backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | "
"fullscreen preview save print | insertfile image media pageembed template link anchor codesample | "
"a11ycheck ltr rtl | showcomments addcomment code",
"custom_undo_redo_levels": 10,
"language": "ru", # To force a specific language instead of the Django current language.
"image_title": "true",
"image_caption": "true",
"automatic_uploads": "true",
}
Можно прописать обработку в файле init_tinymce.js. Я не стал так делать. Хотя можете меня поправить. Если нормально, то можно и в нем. На строчке получения конфига перед инициализацией добавляем коллбек.
mce_conf.file_picker_callback = function (cb, value, meta) {
var input = document.createElement("input");
input.setAttribute("type", "file");
if (meta.filetype == "image") {
input.setAttribute("accept", "image/*");
}
if (meta.filetype == "media") {
input.setAttribute("accept", "video/*");
}
input.onchange = function () {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function () {
var id = "blobid" + (new Date()).getTime();
var blobCache = tinymce.activeEditor.editorUpload.blobCache;
var base64 = reader.result.split(",")[1];
var blobInfo = blobCache.create(id, file, base64);
blobCache.add(blobInfo);
cb(blobInfo.blobUri(), {title: file.name});
};
reader.readAsDataURL(file);
};
input.click();
}
// Вызов редактора
tinyMCE.init(mce_conf);
1.Убираем стандартный вызов
Это был быстрый способ. Я же решил делать это обособлено в дополнительном скрипте. Поэтому комментируем или удаляем конфиг из settings.py
2. Добавляем js
Как добавлять дополнительный css и js я рассказывал в другой статье. В данном случае подключение выглядит так.
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
class Media:
css = {
'all': (
'/static/admin/css/custom.css',
)
}
js = (
'/static/admin/js/custom.js',
)
Сам скрипт. В нем мы убираем редактор из дефолтного вызова tinymce и вызывает новый с нашими настройками. У вас могут быть другие плагины и стиль тулбара эдитора.
if ($('.tinymce').length) {
tinymce.activeEditor.destroy();
}
tinymce.init({
elements: "id_content",
selector: "textarea#id_content",
height: "700",
width: "100%", menubar: "file edit view insert format tools table help",
plugins: "media image preview advlist autolink lists link charmap print preview anchor searchreplace visualblocks code fullscreen insertdatetime media table paste code help wordcount spellchecker",
toolbar: "undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | fullscreen preview save print | insertfile image media pageembed template link anchor codesample | a11ycheck ltr rtl | showcomments addcomment code",
image_title: true,
image_caption: true,
automatic_uploads: true,
image_advtab: true,
language: "ru",
file_picker_types: "image media",
// обработчик загрузчика файла
file_picker_callback: function (cb, value, meta) {
var input = document.createElement("input");
input.setAttribute("type", "file");
if (meta.filetype == "image") {
input.setAttribute("accept", "image/*");
}
if (meta.filetype == "media") {
input.setAttribute("accept", "video/*");
}
input.onchange = function () {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function () {
var id = "blobid" + (new Date()).getTime();
var blobCache = tinymce.activeEditor.editorUpload.blobCache;
var base64 = reader.result.split(",")[1];
var blobInfo = blobCache.create(id, file, base64);
blobCache.add(blobInfo);
cb(blobInfo.blobUri(), {title: file.name});
};
reader.readAsDataURL(file);
};
input.click();
},
content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }"
});
И собственно на этом все.
Примечание: обязательно ставьте курсор на текст иначе будет ошибка при загрузке картинки. Редактор по умолчанию не загружает в папку, а вставляет картинку в BLOB формате
Передо мной стояла задача гибко расширить функционал стандартной админки джанги.
Вьюшка таблицы
Рассмотрим добавление дополнительного действия в лист, на примере пользователей. Стандартный пример — выгрузка пользователей в Exel. Вы видим обычный лист с дефолтными действиями.
Добавим небольшой HTML в шапку. Для этого в admin.py нашего модуля добавим дополнительный класс, который будет переопределять отображение модели в админке.
from django.contrib import admin
from django.contrib.auth.models import User
#Переопределяем вывод модели Юзеров
class CustomAdminUsers(admin.ModelAdmin):
readonly_fields = ['address_report']
list_display = ('first_name', 'email', 'is_active', 'date_joined', 'last_login',)
# определяем дополнительный темплейт
change_list_template = "admin/user_profile_templ.html"
admin.site.unregister(User)
admin.site.register(User, CustomAdminUsers)
change_list_template — собственно директива определяющая дополнительный шаблон. Он находится по пути yourmodule/static/templates/admin
Теперь нужно обработать экшн а admin.py в нашем классе.
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib import messages # сообщения джанги
#Переопределяем вывод модели Юзеров
class CustomAdminUsers(admin.ModelAdmin):
readonly_fields = ['address_report']
list_display = ('first_name', 'email', 'is_active', 'date_joined', 'last_login',)
change_list_template = "admin/user_profile_templ.html" # определяем дополнительный темплейт
# Определяем дополнительные урлы (код сам взял из stackoverflow)
def get_urls(self):
urls = super(CustomAdminUsers, self).get_urls()
custom_urls = [url('^to-exel/$', self.to_exel, name='to_exel'), ]
return custom_urls + urls
# На обработчик
def to_exel(self, request):
# логика экшена
messages.success(request, f"Пользователи успешно выгружены")
return HttpResponseRedirect("../")
admin.site.unregister(User)
admin.site.register(User, CustomAdminUsers)
Собственно все для вьюшки листа.
Вьюшка редактирования модели
Теперь выведем дополнительную информацию в самой странице редактирования. Тут еще быстрее и проще. Ставим поле в readonly. И определяем функцию вывода. Либо текст, либо любой html. Соответсвенно можно брать любую информацию из других моделей и преобразовывать в нужный контент.
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib import messages # сообщения джанги
from django.utils.html import format_html
#Переопределяем вывод модели Юзеров
class CustomAdminUsers(admin.ModelAdmin):
readonly_fields = ['some_content']
list_display = ('first_name', 'email', 'is_active', 'date_joined', 'last_login',)
change_list_template = "admin/user_profile_templ.html" # определяем дополнительный темплейт
# Дополнительный контент
def some_content(self, instance):
return format_html("<a href='%s'>%s</a>" % ('somelink.ru', 'Пример ссылки'))
# Определяем дополнительные урлы (код сам взял из stackoverflow)
def get_urls(self):
urls = super(CustomAdminUsers, self).get_urls()
custom_urls = [url('^to-exel/$', self.to_exel, name='to_exel'), ]
return custom_urls + urls
# На обработчик
def to_exel(self, request):
# логика экшена
messages.success(request, f"Пользователи успешно выгружены")
return HttpResponseRedirect("../")
admin.site.unregister(User)
admin.site.register(User, CustomAdminUsers)
Получаем результат в конце после редактируемых полей.