我的网站搭建(第38天) 用UEditor编辑博文

  • 发布时间:2016年12月19日 17:22
  • 作者:杨仕航

前面开发了用UEditor新增博文。在准备开发用UEditor修改博文时,发现了一些细节问题。可从前面博文看到。现完成了编辑博文功能,具体实现过程看下文。


遵循先前端,再后端,最后调整前端的开发流程。

首先,用UEditor编辑博文需要一个页面。该页面和前面的新增博文页面类似。

可参考中用UEditor新增博文新增博文页面。

不过这里要考虑怎么获取博文的内容,并写入到UEditor中。虽然可以直接把内容写到模版页面中,但显得页面臃肿。这里可以用ajax在加载UEditor的时候,请求博文内容并写入进去。

由于这里的相关链接还未确定,先使用前面新增博客使用到的页面。大致的页面代码如下:

{% extends "blog/index.html" %}
 
{% block blog_head %}
    <script type="text/javascript" src="/ueditor/UE/ueditor.config.js"></script>
    <script type="text/javascript" src="/ueditor/UE/ueditor.all.min.js"></script>
    <script type="text/javascript" src="/ueditor/UE/lang/zh-cn/zh-cn.js"></script>
 
    <script type="text/javascript">
        var ue = UE.getEditor('editor');
    </script>
{% endblock %}
 
{% block blog_content%}
    <form id="blog_form" action="" methord="POST">
        {% csrf_token %}
        <div class="input-group">
            <span id="blog_title_label" class="input-group-addon">博文标题</span>
            <input id="blog_title" name="blog_title" type="text" class="form-control" 
                placeholder="请写入不超过50个字符的标题"
            />
        </div>
 
        <div class="input-group">
            <span id="tag-list-label" class="input-group-addon">分类标签</span>
            <div class="tag-list">
                <ul style="overflow: hidden;">
                    <li class="tag-recommend">
                        <input id="recommend" type="checkbox" name="recommend" />
                        <label for="recommend">推荐</label>
                    </li>
 
                    {% for tag in tags %}
                    <li>
                        <input id="tag_{{tag.id}}" type="checkbox" name="tag" value="{{tag.id}}" />
                        <label for="tag_{{tag.id}}">{{tag.tag_name}}</label>
                    </li>
                    {% endfor %}
                </ul>
            </div>
            <div class="clearfix"></div>
        </div>
 
        <!--用于提交UEditor内容-->
        <input id="content" name="content" type="hidden">
    </form>
 
    <div style="min-height:600px">
        <script id="editor" type="text/plain" style="width:100%;height:500px;"></script>
    </div>
    
    <div style="text-align:right;">
        <span class="tip-text"></span>
        <a id="blog_submit" class="btn btn-primary" href="javascript:void(0);">发表博客</a>
    </div>
{% endblock %}

此处要注意:

1)UEditor的加载,看第9行代码;

2)form表单有3个字段:博客标题(blog_title)、分类标签(blog_tags)、正文内容(content);

3)为了方便提交数据,正文内容字段是一个hidden类型的input标签。提交数据之前,先把UEditor的内容赋值给这个标签;

4)分类标签,即代码中的tags。我采用复选框的形式。该这段是多对多ManyToMany类型,后面处理有些细节会和大家讲解;

5)因为是POST提交(POST提交可以提交很多数据),需要加上{% csrf_token %}。

保存这个文件名为blog_edit.html。其他的相关js代码还没写,后面逐步完善。


接着写后端的代码,这里需要3个函数:

def edit(request, blog_id):
    #打开编辑页面
    pass
    
def edit_content(request, blog_id):
    #获取博文内容
    pass

def edit_ajax(requese, blog_id):
    #修改结果提交
    pass

先定好框架,也为了方便讲解和理解。同时urls.py添加这3个处理方法的请求链接:

from blog import views as blog_views

urlpatterns = [
    #其他无关的链接隐藏不显示
    url(r'^blog_edit/(?P<blog_id>\d+)$', blog_views.edit, name='blog_edit'),
    url(r'^blog_edit_content/(?P<blog_id>\d+)$', blog_views.edit_content, name='blog_edit_content'),
    url(r'^blog_edit_ajax/(?P<blog_id>\d+)$', blog_views.edit_ajax, name='blog_edit_ajax'),
]

urls设置中使用了参数。该参数名要和views.py里面处理相应的处理方法对应参数名字相同。


这里还需要再写个装饰器,判断是否进入编辑博文页面的人是否是该博文的作者。

如果谁都可以进入编辑,那就乱套了。装饰器如下:

#判断对应博文是否当期登录用户发表的
def check_blog_author(func):
    def warpper(request, blog_id):
        try:
            #判断是否有登录
            if not request.user.is_authenticated():
                raise Exception(u'您尚未登录!')

            #判断博文是否存在
            blogs = Blog.objects.filter(id=blog_id)
            if blogs.count == 0:
                raise Exception(u'找不到没有对应的博客')

            #判断发表博客的作者和当前登录的用户是否一致
            blog = blogs[0]
            if blog.author != request.user:
                raise Exception(u'您不是该博文的作者,不能编辑')

            #验证通过,继续执行
            return func(request, blog_id)
        except Exception as e:
            data = {}
            data['goto_url'] = '/'
            data['goto_time'] = 3000
            data['goto_page'] = True
            data['message'] = e.message
            return render_to_response('message.html',data)
    return warpper

由于我的用户系统和博文的作者都是使用Django自带的用户管理系统,可以直接用request.user判断。另外,这里的blog_id参数和那3个编辑处理方法对应。名字也必须一样,因为urls.py设置是这个名字。


写完装饰器就可以写打开编辑博文页面的方法。此处需要把相关博文信息传递过去,例如标题、分类标签等。代码如下:

@check_blog_author(False)
def edit(request, blog_id):
    blog = Blog.objects.get(id=blog_id)

    #处理tag分类标签
    tags = []
    blog_tags = blog.tags.all()
    for tag in Tag.objects.all():
        #给分类标签添加临时属性,表示是否选中该类
        tag.checked = True if tag in blog_tags else False
        tags.append(tag)

    data = {}
    data['blog'] = blog
    data['tags'] = tags
    return render_to_response("blog/blog_edit.html", data, context_instance=RequestContext(request))

这个tag是我blog应用的模型,可参考我前面的博文。Blog和Tag是多对多关系,可用blog.tags.all()获取该博文使用了哪些分类标签。


修改前端页面,才能看到实际效果:

{% extends "blog/index.html" %}
 
{% block blog_head %}
    <script type="text/javascript" src="/ueditor/UE/ueditor.config.js"></script>
    <script type="text/javascript" src="/ueditor/UE/ueditor.all.min.js"></script>
    <script type="text/javascript" src="/ueditor/UE/lang/zh-cn/zh-cn.js"></script>
 
    <script type="text/javascript">
        var ue = UE.getEditor('editor');
    </script>
{% endblock %}
 
{% block blog_content%}
    <form id="blog_form" action="" methord="POST">
        {% csrf_token %}
        <div class="input-group">
            <span id="blog_title_label" class="input-group-addon">博文标题</span>
            <input id="blog_title" name="blog_title" type="text" class="form-control" 
                placeholder="请写入不超过50个字符的标题"
                value="{{blog.caption}}"
            />
        </div>
 
        <div class="input-group">
            <span id="tag-list-label" class="input-group-addon">分类标签</span>
            <div class="tag-list">
                <ul style="overflow: hidden;">
                    <li class="tag-recommend">
                        <input id="recommend" type="checkbox" name="recommend"
                            {%if blog.recommend%}checked{%endif%}
                        />
                        <label for="recommend">推荐</label>
                    </li>
 
                    {% for tag in tags %}
                    <li>
                        <input id="tag_{{tag.id}}" type="checkbox" name="tag" 
                            value="{{tag.id}}" 
                            {%if tag.checked%}checked{%endif%}
                        />
                        <label for="tag_{{tag.id}}">{{tag.tag_name}}</label>
                    </li>
                    {% endfor %}
                </ul>
            </div>
            <div class="clearfix"></div>
        </div>
 
        <!--用于提交UEditor内容-->
        <input id="content" name="content" type="hidden">
    </form>
 
    <div style="min-height:600px">
        <script id="editor" type="text/plain" style="width:100%;height:500px;"></script>
    </div>
    
    <div style="text-align:right;">
        <span class="tip-text"></span>
        <a id="blog_submit" class="btn btn-primary" href="javascript:void(0);">发表博客</a>
    </div>
{% endblock %}

若要让input复选框勾选上的话,只需加入一个checked属性即可。


获取基本信息之后,再写个返回博文内容的请求方法。同样该方法也要验证一下身份,若不验证身份的话,容易被其他人当作接口直接抓取博文内容。

这里判断的代码和刚刚写的装饰器一样。若还使用前面的装饰器有问题,因为前面写的装饰器若验证不通过的话,会跳转到一个消息页面。这个行为就不符合我们要求的json数据。

若重新写一个一样的代码,只是返回的数据不同的装饰器又比较冗余。

可以考虑给这个装饰器加一个参数。判断返回数据的类型:

#判断对应博文是否当期登录用户发表的
def check_blog_author(is_json = False):
    def __check_blog_author(func):
        def warpper(request, blog_id):
            try:
                #判断是否有登录
                if not request.user.is_authenticated():
                    raise Exception(u'您尚未登录!')

                #判断博文是否存在
                blogs = Blog.objects.filter(id=blog_id)
                if blogs.count == 0:
                    raise Exception(u'找不到没有对应的博客')

                #判断发表博客的作者和当前登录的用户是否一致
                blog = blogs[0]
                if blog.author != request.user:
                    raise Exception(u'您不是该博文的作者,不能编辑')

                #验证通过,继续执行
                return func(request, blog_id)
            except Exception as e:
                data = {}

                #判断是否是要返回json
                if is_json:
                    data['success'] = False
                    data['message'] = e.message
                    return HttpResponse(json.dumps(data), content_type = 'application/json')
                else:
                    data['goto_url'] = '/'
                    data['goto_time'] = 3000
                    data['goto_page'] = True
                    data['message'] = e.message
                    return render_to_response('message.html',data)
        return warpper
    return __check_blog_author

带参数的装饰器就复杂一些。给这个装饰器的参数写默认值,就可以不用修改之前的方法。

获取博文内容的方法,代码如下:

@check_blog_author(True)
def edit_content(request, blog_id):
    blog = Blog.objects.get(id=blog_id)

    data = {}
    data['success'] = True
    data['message'] = blog.content
    return HttpResponse(json.dumps(data), content_type = 'application/json')

很少代码即可返回对应的数据。其中,该json有两个参数。1个是success表示是否成功获取到数据;1个是message是保存需要返回的数据。若成功获取,就返回博文内容;若获取不成功,就返回错误信息。


更新前端代码。这里需要更新UEditor初始化部分的代码即可:

<script type="text/javascript">
    //编辑器初始化完成再赋值
    var ue = UE.getEditor('editor');
    ue.ready(function(){
        $.get("{%url 'blog_edit_content' blog.id%}", {}, function(data){
                if(data['success']){
                    //成功则加载博文内容
                    var content = data['message'];
                    ue.setContent(content); 
                }else{
                    //出错则显示错误信息
                    $('.tip-text').text(data['message']);
                }
            }
        );
    });
</script>

到此位置,获取博文的内容完成了。

接下来,只需把博文修改提交即可。此处我还是使用ajax提交数据。为了避免混乱,获取数据和验证数据包装成一个方法。之前写的新增博客获取和验证数据也可使用该方法:

#检查和获取博客新增修改POST提交的数据
def _ajax_post_data(request):
    if request.method != 'POST':
        raise Exception('method error')

    #获取数据
    blog_title = request.POST.get('blog_title')
    blog_tags = request.POST.getlist('tag')
    blog_recommend = request.POST.get('recommend', False)
    blog_content = request.POST.get('content')

    #处理tags
    tag_ids = []
    for tag in blog_tags:
        try:
            tag_id = int(tag)
            tag_ids.append(tag_id)
        except:
            pass

    #验证数据
    if len(blog_title) <=0 or len(blog_title)>=50:
        raise Exception(u'请输入一个不超过50个字符的标题')
    if len(tag_ids)<1:
        raise Exception(u'请选择除了“推荐”之外的至少1个类别')
    if len(blog_content)==0:
        raise Exception(u'尚未写入任何博文内容')
    
    #返回数据
    post_data = {}
    post_data['blog_title'] = blog_title
    post_data['tag_ids'] = tag_ids
    post_data['blog_recommend'] = blog_recommend
    post_data['blog_content'] = blog_content
    return post_data

若验证通过,返回一个包含所需数据的字典。

具体修改博文的方法如下:

@check_blog_author(True)
def edit_ajax(request, blog_id):
    data = {}
    try:
        #检查和获取博客修改POST提交的数据
        post_data = _ajax_post_data(request)

        #修改博文
        blog = Blog.objects.get(id=blog_id)
        blog.caption = post_data['blog_title']
        blog.content = post_data['blog_content']
        blog.recommend = post_data['blog_recommend']
        blog.save()

        #处理多对多,需要博文保存后才能处理
        blog.tags.clear()
        for tag in Tag.objects.filter(id__in = post_data['tag_ids']):
            blog.tags.add(tag)   

        #返回结果
        data['success'] = True
        data['message'] = reverse('detailblog', args = [blog.id,])
    except Exception as e:
        data['success'] = False
        data['message'] = e.message
    
    return HttpResponse(json.dumps(data), content_type = 'application/json')

这里需要注意tags的处理即可,先清空,再添加。


写完后台代码,调整前端页面。加上ajax提交代码:

<script type="text/javascript">
    $("#blog_submit").click(function(){
        //检查数据
        if(!check_data()) return false;
        $('.tip-text').text('修改博客中...');
        $("#blog_submit").addClass('disabled');

        //把UEditor内容写入hidden中
        var ue = UE.getEditor('editor');
        $('#content').val(ue.getContent());

        //ajax提交表单
        $.ajax({
            type: "POST",
            data: $('#blog_form').serialize(),
            url: "{% url 'blog_edit_ajax' blog.id%}",
            cache: false,
            dataType: "json",
            success: function(json, textStatus) {
                if(json['success']){
                    //跳转页面
                    $('.tip-text').text('修改博文成功,跳转页面中...');
                    var url = json['message'];
                    window.location.href = url;
                }else{
                    //显示错误信息
                    $('.tip-text').text('修改博客出错:'+json['message']);
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                $('.tip-text').text('修改博客出错,请检查或稍后再试');
            }
        });
        $("#blog_submit").removeClass('disabled');
    });

    function check_data(){
        $('.tip-text').text('');
        //标题
        var title = $('#blog_title').val();
        if(title.length<=0 || title.lenght>=50){
            $('.tip-text').text('请输入一个不超过50个字符的标题');
            return false;
        }
        //类别
        if($('input[name=tag]:checked').length==0){
            $('.tip-text').text('请选择除了“推荐”之外的至少1个分类');
            return false;
        }
        //内容
        if (!UE.getEditor('editor').hasContents()){
            $('.tip-text').text('尚未写入任何博文内容');
            return false;
        }
        return true;
    }
</script>

这里的代码包含前端验证数据和ajax提交数据。基本和之前的新增博文代码一样。


最后,在博文显示页面添加入口链接即可。

<a href="{%url 'blog_edit' blog.id%}">编辑</a>

若你只是想让作者看到该链接,其他人看不到。需要在响应打开博文页面的方法,添加如下代码:

#是否显示编辑链接
show_edit_url = False
if request.user.is_authenticated():
    if request.user == blog.author:
        show_edit_url = True

再将该变量传递给模版页面,模版页面用if判断即可。

上一篇:Python的json模块基本用法

下一篇:主板给HDMI显示器供电

评论列表

智慧如你,不想发表一下意见吗?

新的评论

清空

猜你喜欢

  • 猜测中,请稍等...