涉及知识点:
- ckeditor的使用
- 上传文件save方法的重写,实现图片添加水印功能
- 前端富文本编辑器的展示逻辑
富文本编辑器实现机制:前端渲染一个新的编辑器界面,隐藏原有的界面,但又能将编写的内容同步到原有的编辑器中,最后由原有的编辑器提交数据
富文本工具:django-ueditor或者django-ckeditor
1.安装配置
pip install django-ckeditor==5.4.0
pip install pillow==5.1.0
# 注册app
'ckeditor', # 富文本编辑器
'ckeditor_uploader', # 配置图片上传
# 配置ckeditor
CKEDITOR_CONFIGS = {
'default': {
'toolbar': 'full',
'height': 300,
'width': 800,
'tabSpaces': 4,
'extraPlugins': 'codesnippet', # 配置代码插件
},
}
有关django-ckeditor的详细配置:https://github.com/django-ckeditor/django-ckeditor
2.配置 adminforms.py
针对需求前端展示对应的编辑器界面,隐藏原生的编辑器界面
from django import forms
from ckeditor.widgets import CKEditorWidget
from ckeditor_uploader.widgets import CKEditorUploadingWidget
# 自定义编辑页面中表单元素的样式,调用自动补全接口
class PostAdminForm(forms.ModelForm):
# 省略其余代码
# content = forms.CharField(widget=CKEditorWidget(), label='正文', required=True)
# 这个插件是带图片上传功能的,上面的只能处理文字,上传的同名图片,ckeditor会自动追加随机码区分
# 这边的required都要写成false,因为用户最终提交的时候,总有两个输入框是空的
# 原生的content字段设置隐藏,但在数据清洗阶段会接收到最终的内容
content = forms.CharField(widget=forms.HiddenInput(), label='正文', required=False)
# 增加额外的forms组件
# 以下两个组件根据is_md的值通过js进行切换展示
content_ck = forms.CharField(widget=CKEditorUploadingWidget(), label='正文', required=False)
content_md = forms.CharField(widget=forms.Textarea(), label='正文', required=False)
# 配置Meta,在fields中增加新增的字段
class Meta:
model = Post
# 需要自动补全的字段要放到前面
fields = ('category', 'tag', 'title', 'desc', 'is_md',
'content', 'content_ck', 'content_md', 'status')
def __init__(self, instance=None, initial=None, **kwargs):
# initial为form对象中各字段初始化的值
initial = initial or {}
# 如果是编辑一篇文章,那么instance是当前文章的实例
if instance:
# 基于instance对象,对新增的两个form层字段进行处理
if instance.is_md:
initial['content_md'] = instance.content
else:
initial['content_ck'] = instance.content
super().__init__(instance=instance, initial=initial, **kwargs)
# 在clean方法中对用户提交的内容进行处理
def clean(self):
is_md = self.cleaned_data.get('is_md')
# 根据is_md的值,判断用户是在哪个编辑器中输入的,然后取对应的内容
if is_md:
content_field_name = 'content_md'
else:
content_field_name = 'content_ck'
# 此处content为取到的markdown或ckeditor中的内容
content = self.cleaned_data.get(content_field_name)
# 在此处进行空值控制,若为空则返回异常
if not content:
self.add_error(content_field_name, '必须填')
return
# 将取到的内容赋值给原生的content字段
self.cleaned_data['content'] = content
return super().clean()
class Media:
# 引入js文件,控制编辑器的展示逻辑
js = ('js/post_editor.js',)
3. settings.py 配置上传路劲和访问url
# 分别配置ckeditor图片访问起始url,上传保存路径和存放位置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
CKEDITOR_UPLOAD_PATH = 'article_images'
4.配置接口 urls.py
from django.conf.urls import url, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
# 。。。。。。
# 提供上传图片和浏览图片的接口
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
# 配置图片资源的访问,使用django内置的静态文件处理功能提供静态文件服务
# 在正式环境中,这个功能由Nginx来完成
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
5.对上传的图片添加水印
django默认的存储方式是文件存储,重写save方法即可对上传的文件进行处理
项目主应用下新增 storage.py
from io import BytesIO
from django.core.files.storage import FileSystemStorage
from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image, ImageDraw, ImageFont
# 给上传的图片添加水印
class WatermarkStorage(FileSystemStorage):
# 重写save方法,添加水印
def save(self, name, content, max_length=None):
if 'image' in content.content_type:
# 将文件对象转为图片对象,然后添加水印
image = self.watermark_with_text(content, 'Master-Sun', 'red')
# 再将图片对象转为文件对象
content = self.convert_image_to_file(image, name)
# 调用父类的save方法
return super().save(name, content, max_length=max_length)
def convert_image_to_file(self, image, name):
temp = BytesIO()
image.save(temp, format='PNG')
file_size = temp.tell()
return InMemoryUploadedFile(temp, None, name, 'image/png', file_size, None)
# 字体参数fontfamily可指定本地字体文件路径
def watermark_with_text(self, file_obj, text, color, fontfamily=None):
image = Image.open(file_obj).convert('RGBA')
draw = ImageDraw.Draw(image)
width, height = image.size
margin = 10
if fontfamily:
font = ImageFont.truetype(fontfamily, int(height / 20))
else:
font = None
textWidth, textHeight = draw.textsize(text, font)
x = (width - textWidth - margin) / 2 # 计算横轴位置
y = (height - textHeight - margin) # 设置纵轴位置
draw.text((x, y), text, color, font)
return image
6.修改默认的存储引擎 settings.py
# 修改默认的存储引擎
DEFAULT_FILE_STORAGE = 'blogidea.storage.WatermarkStorage'
7.模型层,新增字段判断使用markdown还是ckeditor,并重写save方法
import mistune
from django.db import models
class Post(models.Model):
# ......
# 新增字段,用来标注使用markdown还是富文本编辑器
is_md = models.BooleanField(default=False, verbose_name='markdown语法')
# 重写save方法,根据is_md的值切换存储格式
def save(self, *args, **kwargs):
if self.is_md:
self.content_html = mistune.markdown(self.content)
else:
# 如果是非markdown语法的,则直接保存content即可(ckeditor已做好转换)
self.content_html = self.content
super().save(*args, **kwargs)
7.自定义js文件实现编辑器的展示逻辑
(function ($) {
var $content_md = $('#div_id_content_md');
var $content_ck = $('#div_id_content_ck');
var $is_md = $('input[name=is_md]');
var switch_editor = function (is_md) {
if(is_md){
$content_md.show();
$content_ck.hide();
}else{
$content_ck.show();
$content_md.hide();
}
};
// is_md监测点击事件,点击后执行switch_editor函数切换编辑器
$is_md.on('click', function () {
switch_editor($(this).is(':checked'));
});
// 首次加载完页面后执行switch_editor函数确认展示的编辑器
switch_editor($is_md.is(':checked'));
// xadmin已经加载了jQuery,可以直接使用,这里的jQuery中的Q要大写
})(jQuery);
- adminx.py 中修改layout配置
# xadmin关于编辑页的布局设置
form_layout = (
Fieldset(
'基础信息',
Row('title', 'category'),
'status',
'tag',
),
Fieldset(
'内容信息',
'desc',
'is_md',
'content_ck', # 新增的编辑器,但只会显示一个
'content_md',
'content',
),
)
参考:《Django企业开发实战》