关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
前面开发了用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判断即可。