zoukankan      html  css  js  c++  java
  • python2.0_day21_bbs系统评论自动加载+文章创建

    day20中我们已经实现了bbs系统的前端展示,后台admin管理,以及前端动态显示顶部登录和评论的分级展示功能.其中评论的分级展示功能最为复杂.
    上一节中我们只是在文章明细页面中加了一个button按钮,能够获得该文章的评论内容.但是我们知道正常的论坛网站,在打开某一篇文章的时候就会显示该文章的评论内容.
    本节我们主要实现的几个功能:1.评论自动加载 2.可以添加子评论. 3.评论框的动态显示 4.新文章的建立要求使用富文本编辑框 5.Django框架通过前端页面上传文件的用法.

    一首先我们看上节中我们实现获取评论的代码,就是templates/bbs/article_detail.html页面中js;
    有这么一个标签:
        <button type="button" onclick="GetComments()">测试获取评论</button>
        然后定义了一个js:
        function GetComments(){
            $.get("{% url 'get_comments' article_obj.id %}",function(callback){
                console.log(callback);
                $(".comment-list").html(callback);
            });
        }
        那么我们想让他自动加载,无非就是在页面加载后调用一下,也就是加载到那个$(document).ready(function(){})中,等加载好文档树后在function中执行
            $(document).ready(function(){
                    GetComments(); //页面加载后,调用此函数获取当前文章的的评论
                    $(".comment-box .btn").click(function(){
                        var comment_text = $(".comment-box textarea").val();
                        if (comment_text.trim().length < 5){
                            alert("评论不能少于5个字sb")
                        }else{
                            //post
                            $.post("{% url 'post_comment' %}",
                                    {
                                        'comment_type':1,
                                        'article_id':"{{ article_obj.id }}",
                                        parent_commet_id:null,
                                        'comment':comment_text.trim(),
                                        'csrfmiddlewaretoken':getCsrf()
                                    },//end post args
                                    function(callback){
                                        console.log(callback)
                                        if (callback == 'post-comment-success'){
                                            alert('successful')
                                        }
                            });//end post
                        };
                    });//end button click
    
                });
        还有当我们新创建一个评论时也要把新评论的内容加载出来.所以我们应该在评论添加成功后,再次调用GetComments()函数.所以最后是在回调函数中调用.
    于是上面的代码改成:
            $(document).ready(function(){
                    GetComments(); //页面加载后,调用此函数获取当前文章的的评论
                    $(".comment-box .btn").click(function(){
                        var comment_text = $(".comment-box textarea").val();
                        if (comment_text.trim().length < 5){
                            alert("评论不能少于5个字sb")
                        }else{
                            //post
                            $.post("{% url 'post_comment' %}",
                                    {
                                        'comment_type':1,
                                        'article_id':"{{ article_obj.id }}",
                                        parent_commet_id:null,
                                        'comment':comment_text.trim(),
                                        'csrfmiddlewaretoken':getCsrf()
                                    },//end post args
                                    function(callback){
                                        console.log(callback)
                                        if (callback == 'post-comment-success'){
                                            //alert('successful');
                                            GetComments();//新的评论添加成功后,再次刷新评论.
                                        }
                            });//end post
                        };
                    });//end button click
    
                });
        完成上面的代码.我们就可以实现自动加载页面和提交评论后自动加载页面了.紧接着问题来了.我们这里只能添加一级评论,不能添加子评论.接下来我们就来实现如何添加子评论.
    我们先为每一个评论加一个评论按钮吧.因为评论内容都是在后台生成后直接返回给前端页面是html文本内容.所以我们要为每一个评论再加一个"回复"按钮,也要在后台的函数中修改添加.
    这里又一个问题 ,我们要为每一条评论添加一个onclick事件.同时又要考虑,当你新加评论时使用的是ajax方式,而不是form方式,所以js是不会再次执行的.也就是新加的评论不会绑定onclick事件.
    所以此时就要用到day14节中提到的知识委托.委托的核心概念就是监控上级标签下的指定标签类型,一旦子标签有变化就会重新绑定.
    理论上我们就可以这么做.但是我们先从普通的绑定方法下手.看看究竟会发生什么?

    查看我们更改的后台的函数的更改的部分

        按照常理来说,这里应该是绑定了click事件,但是通过代码来看却没有绑定,这是什么问题呢?
    原因是这样的,关于评论的获取也是通过调用js获取的.而我们绑定事件也是通过js实现的.那么问题来了.html语言解释器(浏览器)虽然是自上而下解释代码的,但是对于获取评论的js函数是非阻塞的,也就是调用GetComments()函数后,就立马之行绑定函数.
    问题就在于javascript里的函数都是非足赛的,那么在前面一个函数还没执行完成,即还没有返回评论相关的html代码,后面就开始绑定,当然是找不到任何关于.add-commnet的标签了.所以上图显示无任何click事件.
    紧接着问题来了,那么我们就应该在GetComments()函数返回结果后,在进行绑定函数的调用.那么怎么判断之行完成了呢.最简单的就是不判断,把绑定这部分代码直接放到GetComments()函数中去.放到回调函数中不就行了.

        紧接着我们测试下结果:

        我们随便点点,看到结果打印了.
    二使用js里的委托方法实现上述功能
    那么问题来了,我们如果用委托,并且不写在GetComments()函数里面是不是也能实现,必定委托是有监控作用的.
    我们来测试下,把代码改成如下:

        访问测试结果:

        我们看使用委托绑定事件的方法也是可以实现的.都可以,那么我们就用委托把.
    三/ 绑定事件完成,我们把事件的内容换成把评论框拉到该评论的下方.
    我们看在代码中我们在.comment-box类下就写了一个评论框,我们考虑能不能在点击回复图标的时候,就在这个标签的下面克隆一个评论框.前提的评论框和提交按钮这两个标签应该在一个div下才好克隆,
    于是我们在这两个标签上层加一个div标签,class='new-comment-box'类.代码如下:

        接下来我们就要在绑定事件的地方,实现克隆这个new-comment-box标签了
    代码如下:

        我们看看测试结果

        我们看到克隆是成功了,但是存在两个问题,问题1.是之前的那个在comment-box里的应该删掉, 问题2 另一个是多个评论框既然能同时存在.
    我们先解决问题1
    我们可以在克隆后,把前面一个new-comment-box删除.于是代码改成如下:

        访问测试:

        这就实现了同时只有1个评论输入框.
    但是还是有问题,当我们想对文章进行评论,而不是对评论进行评论.怎么办.现在看来是不行.
    其实我们可以在评论处也加一个按钮,实现文章旁边的图标,再把评论输入框移过来.代码如下

        这下我们看效果如图

        其实现在还存在三个问题:
    1.我们使用clone()虽然把标签克隆到新的位置了,但是button标签的绑定事件是没有克隆的,也就是说只要clone一次评论输入框后,提交就失效了.
    那么如何解决把click事件也克隆呢?
    2.我们之前做提交评论时,使用的是ajax异步提交.提交时父级评论的id也就是parent_commet_id:null,默认都是没有的,现在要根据实际情况加上.
    3.我们知道当我们点击评论图标时候会有评论输入框,点击输入提交后,在点击任何评论图标都是没有反应的.这个是为什么,怎么解决?
    我们先解决问题1
    三种方案,方案一直接在button中加一个onclick事件属性.方案二:每次克隆后,在绑定一次. 方案三.clone()函数直接加一个参数就可以把click事件也克隆了.
    我们使用方案三 .clone(true) 加一个true即可.
    代码如下:


    下面我们来解决问题2 ,为评论添加子评论.
    此问题解决不难,只要在用$.post提交时获取到父级评论的id即可.js 代码如下:


    解决问题3 评论提交后为什么评论框就消失了?
    因为提交评论后我们在JS代码中又再次之行获取评论的方法.而获取评论的方法会把comment-list里的html代码覆盖掉.从而导致new-comment-box这个评论输入框和提交评论的按钮div消失了.
    所以你在点也没有用.解决办法就是在提交评论后把.new-comment-box 这个类的标签插入到comment-box标签的内部的前面(之所以是内部的前面是因为之前就放这).
    代码如下

        至此评论这边就没啥问题了.

    四bs创建新贴页面开发
    首先我们要在现有的页面中添加一个创建新帖的入口 a标签.所以我们要修改base.html,放在右上角
          <ul class="nav navbar-nav navbar-right">
            {% if request.user.is_authenticated %}
              <li><a href="#">{{request.user.userprofile.name}}</a></li>
              <li><a href="{% url 'logout' %}">logout</a></li>
            {% else %}
              <li><a href="{% url 'login' %}">登录/注册</a></li>
            {% endif %}
            <li class=""><a href="{% url 'new-article' %}">发贴</a></li>
          </ul>
        然后在bbs/urls.py文件中创建这个路由条目且别名为new-article
    如:
            urlpatterns = [
                url(r'^$', views.index),
                url(r'^category/(d+)/$',views.category,name='category_detail'),
                url(r'^detail/(d+)/$',views.ariticle_detail,name='article_detail'),
                url(r'^comment/$',views.comment,name='post_comment'),
                url(r'^comment_list/(d+)/$',views.get_comments,name='get_comments'),
                url(r'^new_article/$',views.new_article,name='new-article'),
            ]
        其次在视图文件bbs/viewspy中创建new_article函数.如:
        def new_article(request):
            if request.method=="GET":
                return render(request,'bbs/new_article.html') #如果是GET就是浏览创建新贴的页面,如果是POST才是提交.
        然后我们在创建这个页面.内容如下:
            {% extends 'base.html' %}
    
            {% load custom_tags %}
    
            {% block page-container %}
                <div class="wrap-left">
                dddd
                </div>
                <div class="wrap-right">
                    sss
                </div>
                <div class="clear-both"></div>
    
            {% endblock %}
        我们访问首页和点击"发帖"查看跳转页面如下:


    接下来我们来使用modelform来实现新帖的创建,和后端数据的验证.
    modelform 的使用可以参考 之前的章节 python2.0_day19_充分使用Django_form实现前端操作后台数据库

    1.首先创建bbs/forms.py文件,并添加如下内容:
            #_*_coding:utf-8_*_
            __author__ = 'ZhouMing'
    
            from django.forms import Form,ModelForm
            from bbs import models
    
            class ArticleModelForm(ModelForm):
                class Meta:
                    model = models.Article
                    exclude = ()

    2.在bbs/views.py文件中引用ArticleModelForm类,实例化对象并返回给前端
            def new_article(request):
                if request.method=="GET":
                    form_obj = forms.ArticleModelForm() #实例化form类
                    return render(request,'bbs/new_article.html',{'form_obj':form_obj,})


    3.接着我们更改templates/bbs/new_article.html文件内容如下:
            {% extends 'base.html' %}
            {% load custom_tags %}
            {% block page-container %}
                <div class="wrap-left">
                    {{ form_obj }}  #这里我们前端模版语言取到这个form_obj
                </div>
                <div class="wrap-right">
                    sss
                </div>
                <div class="clear-both"></div>
            {% endblock %}

    4.访问测试如图:

        我们看前端已经获取到了form类,接下来还是先美化下.
    还是form的知识点,我们知道form不仅提供字段数据还提供html代码,所以改样式也要在form类中改,于是更改bbs/forms.py文件如下:
           #_*_coding:utf-8_*_
            __author__ = 'ZhouMing'
    
            from django.forms import Form,ModelForm
            from bbs import models
    
            class ArticleModelForm(ModelForm):
                class Meta:
                    model = models.Article
                    exclude = ()
    
                # 先继承,再重写(想重新定义某些字段的属性,必须先继承,在重写,就这样记着)
                def __init__(self,*args,**kwargs):
                    super(ArticleModelForm,self).__init__(*args,**kwargs)
                    # self.fields['qq'].widget.attrs["class"] = "form-control"
    
                    # for循环每一个字段,修改字段的class属性
                    for field_name in self.base_fields:      #self.base_fields说白了就是把所有字段取出来但是是字典的形势
                        field = self.base_fields[field_name]
                        #这里可以是上面的例子field.widget.attrs["class"] = "form-control",也可以用update,用update的好处就是可以同时修改多个属性了
                        field.widget.attrs.update({'class':"form-control"})  # class = form-control 这是bootstrap里的样式.

    5.我们再次访问测试结果如下:

            我们看样式变得好看了.但是这里要引入一个新知识点.就是 帖子的正文,不能单纯的是输入框,而是富文本编辑框.
    富文本编辑框我们可以使用百度的富文本编辑器.
    有几个比较出名的:国内的有一个百度出的叫做 ueditor,但是它默认不支持Django,支持java和PHP 和c++,如果想支持Django,但是很麻烦.有时间还是要研究下,因为它很强大,做得就跟word似的.
    现在用的比较多的是国外的一款ckeditor,基本功能够用,它也有很强大的功能,但是是付费的.有时间还是去研究下ueditor
    我们这里就使用ckeditor了,访问http://ckeditor.com/download,下载ckeditor.
    这个安装使用非常简单,直接把下载下来的包,拷贝到statics目录下,创建一个新目录为plugins.然后把整个ckeditor目录放到statics/plugins/ckeditor
    我们来看应该在html代码中如何调用:

            我们按照上面的配置方法来实现看看效果先.

            我们看到效果了,我们让这个富文本替换我们modelform里帖子文本字段,就好了.
    我们看form里返回给前端的文章内容输入框的标签是有id的我们只需要把上面替换的内容改成这个id就好了

            访问页面结果如图:

            然后把不必要的字段都给屏蔽掉.比如:作者发布时间优先级等
    修改bbs/forms.py文件如下:

            我们来看看结果:

            上图中我们看到可以传图片.这是一个新知识点!你会说后台不就可以传图片嘛!这怎么会是新知识点呢?问题就在于后台可以传,用form后在前端传图片就有问题了.
    具体问题如图:
    首先我们要在前端加一个提交的按钮commit
    如图

            加一个form标签.
    后端还要在视图函数中添加POST请求类型的处理方法.
               def new_article(request):
                    if request.method=="GET":
                        article_form = forms.ArticleModelForm() #实例化form类
                        return render(request,'bbs/new_article.html',{'article_form':article_form,})
                    elif request.method == 'POST':
                        print(request.POST)
                        article_form = forms.ArticleModelForm(request.POST)
                        if article_form.is_valid():
                            article_form.save()
                        else:
                            return render(request,'bbs/new_article.html',{'article_form':article_form,}) 这里返回的article_form,就是进行验证后的内容了所以错误会在相应的标签中显示
                        return HttpResponse('创建成功')
            我们在页面上填写信息,并且上传图片.结果在提交的时候图片虽然已经上传但是还是提示要上传,情况如下:

            这是为什么呢?
    我们先来看看后台打印出来的request.POST里有没有上传的图片信息:

            我们看其实后台是接收到前端上传文件的信息了,但为什么还提示错误呢?
    注意了,原因是如果在Django中从前端上传图片或者文件到后台,是要单独的使用request.FILES获得的,而不是request.POST里获取的.
    甭管前端是通过FORM方式传还是通过ajax方式上传,都是使用request.FILES获取,这一点要彻底记住.
    更改视图如下:

            我们把视图函数改下在看看结果如何:

            我们在看看后端视图的打印内容:

            为空,此时后端是没有问题的,已经通过request.POST获得都 了,那么为什么为空?原因是前端没有传过来.
    这又有一点要记住的:如果你在前端需要提交文件或者图片到后端,form的格式要改,改成如下:

            这是一个前端的知识点
    现在我们再次测试提交图片看看结果:

            看到上面的错误信息,最起码说明图片我们上传成功了,并且后端也接收到了,这个错误的原因知识在使用form保存数据到models.Article类时有必填的值没填.
    在之前的章节中我们学过 form对象.cleaned_data 以字典的方式显示form表单里提交过来的数据.
    记住form对象.cleaned_data 是一个属性,你不能对它进行更改和新加元素.
    另外 cleaned_data属性只能在form实例调用过.is_valid() 方法后才能使用,否则不显示字典.
    那么既然我们不能对form对象里的元素进行更改.也就意味着 前端传过来啥子form就是啥子,后面不能加元素,那么就不能使用 form对象.save()进行添加models对象了.
    于是我们想添加元素的思路只能是先获取form里的字典,然后添加元素,在调用models生成对象,使用models对象.save()方法.

            再次提交,点击创建就成功了.前提是你登录过了,如果你没登录过是没办法使用request.user.userprifile.id获得用户id的.
    当然我们要为此视图函数加上用户验证的装饰器.
    这里又多了一个知识点:装饰器@login_required(login_url='/login/') 直接指定如果没登录会跳转到哪个url
    这个low,因为如果有多个函数要做验证,你就要在每一个上面写这个.
    有没有办法,只写一次,其它函数调用的时候还是像以前一样调用装饰器@login_required
    有,就是在settings.py文件里设置.

            这里我们之所以url写成'/login/',是因为我们前面在提交评论时写了登录页面的url是这个,
    而使用@login_required装饰器对视图函数验证时Django框架里默认的url是'/accounts/login/'
    我们上图改的就是为@login_required装饰器更改验证不通过时跳转的登录url.
    下图中表示我们自己写的前端创建新贴成功,但是还是存在一定的问题.就是我们的文档内容却是标签,如图

            那么为什么是标签呢?因为富文本编辑框存通过form提交到post到后台,后台存的字段内的值就是标签.
    那么我们在前端就需要把这些带有标签的字段渲染成html页面内容.怎么渲染呢,这里又有一个前端知识点:使用内置的filter标签|safe
    我们要在article_detail.html页面展示文章内容,所以我们就要把|safe 添加到article_detail.html页面中,具体位置如下图:

            我们再次刷新页面看结果如图:

            前端显示列表时应该反转,让最新发布的帖子放在前面.于是我们修改index.hmlt的页面代码如下:

            结果:


    四详细介绍前端创建文件
    上面的我们用form类实现了上传文件.但是实际开发中我们可能有不用form类的时候.也就是需要我们写上传文件的前端和后端.
    首先对于上传文件,官网上有那么一段英文解释:
    Note that request.FILES will only contain data if the request method was POST and the <form> that posted the request has the attribute enctype="multipart/form-data". Otherwise, request.FILES will be empty.
    意思就是:使用request.FILES获得上传文件的前提条件两个: 1.前端POST方式提交的 2.FORM表单要有enctype="multipart/form-data"参数
    但是你知道文件是怎么从浏览器中读到后台的吗?当上传的文件很大的时候.如果通过form表单传文件的时候,你会发现,一点summit按钮就白屏了.
    为什么会白屏呢?因为浏览器要花很长时间把文件读进去并且发送给服务端.我们知道大白屏很不友好.那么用什么方式提交比较好呢?当然是ajax异步提交了.现实中的例子,百度的云盘如果提交上传一个2G文件,它就不是白屏,不然2小时白屏,用户会疯的.
    百度云盘会有一个进度条,然后在那不停的上传,这就是采用ajax异步上传实现的.进度条的实现和我们之前学习过的socket上传实现原理是一样的.就是前端上传多少纪录下来,然后显示百分比.
    关键是后端怎么处理,后端如果用form,form对象.save()就一次性保存了.另外一个问题,如果文件是2M后台可以先把接收到的文件放到内存中,但如果是2G,有10个用户都在上传,那服务器还不爆炸.
    所以后台要自己写解决上面的问题.
    首先Django考虑到大文件的问题,并且Django框架内部有规则:如果你上传的文件小于2.5M,是可以保存在内存中的.直至文件接收完成后保存在upload文件夹下.所以此时速度非常快
    如果文件大于2.5M,Django默认会把接收到的文件存在一个临时目录里.这个临时目录在unix系统中的/tmp/目录下

    我们看下例子:
    实现在new_article.html页面中添加一个form表单用于上传,上传的URL可以新定义一个,这样就可以提供给所有需要上传的页面来调用了.
    new_article.html页面添加内容如下:

            bbs/urls.py文件内容添加如下:

            bbs/views.py文件视图函数添加如下:

            我们分别上传一个小于2.5M的文件,和一个大于2.5M的文件,看看前端和后端的反应
    前端反应:当文件较大时,上传时卡住了. 这个用户体验很差

            后端:
    当文件小于2.5M时打印print(request.FILES)结果如图

            当文件大于2.5M时打印print(request.FILES)结果如图

            了解到Django对大小文件上传的处理方式不一样后,下面我们要解决的是怎么把这些文件真正存下来.
    Django官方上实现了方法,如图:

            于是我们的视图函数参照官网,更改成如下:

            这时候我们在做上传就会发现upload目录下有文件了.并且我们看下图知道,这是前端边上传,后端边保存的.

            至此上传文件的后端操作就结束了.接下来想实现进度条的效果,就需要将form改成ajax的方式了.具体怎么做,当作作业回去研究.

    五在页面顶部如何提示有"n条新消息".
    我个人在使用Django框架,开发系统时,有一点不清楚的:比如一个新功能涉及到urls.py文件views.py文件还有html文件.
    那对于一个功能的开发,到底从哪一个开始入手,能不能总结出一套方法呢.
    能,我觉得我找到方法了.
    1.主要看request请求的出发点.
    浏览器输入网址 -> 更改urls.py文件 -> 更改视图函数文件views.py -> 请求的返回返回json数据或者返回新的模版页面*.html
    页面中a标签的点击 -> 更改urls.py文件 -> 更改视图函数文件views.py -> 请求的返回返回json数据或者返回新的模版页面*.html
    js中$.post和$.get 方式的提交 -> 更改urls.py文件 -> 更改视图函数文件views.py -> 请求的返回返回json数据
    form 的post和get的action提交地址 -> 更改urls.py文件 -> 更改视图函数文件views.py -> 模版页面*.html
    所以无论哪中方式都要从请求的出发点入手.
    总结:无论是什么请求,都可以归纳成上面四步:
    1.请求的起点
    2.编辑urls.py路由
    3.后台views.py中的函数编辑,处理后返回给请求源.
    4.前端页面获得数据进行渲染展示.
    所以这里我们的思路是:
    在js中定义一个定时任务,在定时任务里使用$.getJSON方法获取后视图函数查找到的新帖子的数量.
    1.首先是先要写的是这个定时任务,以及定时人中中$.getJSON方法访问的url和传递的参数.
    2.然后更改urls.py文件,添加$.getJSON请求的URL
    3.写后台的视图函数.由于使用的$getJSON方法获取的,所以返回json的数据即可
    4.既然没有新页面,请求的源页面拿到数据后进行展示即可.在改$.getJSON中的回调函数即可.
    所以实现上述四步的实际代码是:
    首先在index.html中写定时任务如图:

        然后我们要在urls.py中添加上图中$.getJSON()请求的地址

        然后我们在写后台处理的函数.

        最后就是编辑回调函数,实现有新消息显示在顶部,没有则不显示.在这之前我们先看看后台返回的数据前端能接收到吗?
    如图:

        我们从图中可以看到,js的定时任务执行成功,并且后台以json格式返回新帖的数量.
    接着我们就可以更改回调函数,把得到的新贴数量的消息展现在前端了.

        这里为什么要在$(“.wrap-left”).children()[1].attr(‘article_id’)的外层在加一个$()呢?
    因为你用切片[1]获得的是一个标签,而不是一个document 的obj了。就不能调用
    其它方法了,在外面加一个$()是把一个标签实例化成一个obj了,在次能够使用方法了.
    这时候我们看看访问效果如图:

        至此我们的bbs的一些基本页面算是ok了,接下来就是实现WEB聊天了.










  • 相关阅读:
    l2tp ubuntu
    my emacs fav config
    2048小游戏源码(vue自定义指令使用)
    Vue 脚手架新建项目
    vue中修改router定义的name值
    只能输入金额格式的input
    前端开发中UI问题处理
    form表单提交Ajax请求后不跳转
    小程序中代替v-html用法
    小程序中分页加载问题
  • 原文地址:https://www.cnblogs.com/zhming26/p/5898796.html
Copyright © 2011-2022 走看看