Django评论

评论复杂的地方在于需要实现点击提交评论后评论内容需要立刻出现在下面,还要保持页面位置不变,所以提交后不能整体刷新页面,因为刷新以后页面肯定在最上面,而评论一般都在最下面,所以要用到Ajax

整个过程用到了Django,Vue.js,reqwest,REST_framework,ajax

展示评论内容

展示评论内容可以直接用Django从数据库中取出数据,然后在view中渲染到前端,但这里我想用Vue.js,为了减少django的工作量,提高效率吧

// pinglun.js
let vue = new Vue({
    el : "#app",
    data : {
        pinglun : [
            {'评论者':'zhangsan','评论日期':'2019-6-5','评论时间':'17:47:23','评论内容':'hahahha','对应文章_id':'1'},
            {'评论者':'zhangsan1','评论日期':'2019-6-5','评论时间':'17:48:23','评论内容':'hahffahha','对应文章_id':'1'},
        ],
    },
})
<!--pinglun.html-->
<div id="app">
    <div class="alert alert-secondary" role="alert" id="pinglunlist">
        <div v-for="item in pinglun " >
            <h5>{{ item.评论者 }}</h5>
            <p>{{ item.评论内容 }}</p>
        </div>
    </div>
</div>

这样js中data的数据就可以渲染到html页面了,但我们需要从数据库中拿到数据,并且赋值给data中的pinglun,这里需要一个reqwest模块,需要下载

npm i reqwest

下载之后访问json文件里面的那个网址,下载压缩包,解压后里面有一个reqwest.js文件,要把这个文件引入,就和用Vue要引入Vue.main.js一样,reqwest可以从一个url请求数据,并且返回

// 这是官方api
reqwest({
    // 要请求的路径
    url: 'path/to/html'
    // 请求方式
  , method: 'post'
    // 请求时要携带的数据
  , data: { foo: 'bar', baz: 100 }
    // 成功请求的回调函数
  , success: function (resp) {
      // reap中就包含请求来的数据
      qwery('#content').html(resp)
    }
})
reqwest({
    url: 'path/to/html'
  , method: 'get'
  , data: [ { name: 'foo', value: 'bar' }, { name: 'baz', value: 100 } ]
  , success: function (resp) {
      qwery('#content').html(resp)
    }
})

应为需要有一个请求的url,所以还需要做一个api接口,这里有两种办法,一种是用Django提供的HttpResponse和json直接将序列化后的json数据渲染到页面,但这样只能渲染成json类型,并且存在文字编码的问题,还可以使用django-rest-framework框架,Django REST框架是一个功能强大且灵活的构建Web api工具包

使用 HttpResponse ,不推荐



# urls.py
path('api/json',views.injson),

# views.py
def injson(request):
    # 这里的info是手写的假数据,若使用这种方法可以从数据库中获取相应数据再用json.dumps序列化
    info = [{'评论者':'zhangsan','评论日期':'2019-6-5','评论时间':'17:47:23','评论内容':'hahahha','对应文章_id':'1'},
            {'评论者':'zhangsan1','评论日期':'2019-6-5','评论时间':'17:48:23','评论内容':'hahffahha','对应文章_id':'1'},]
    return HttpResponse(json.dumps(info))

使用REST框架

先要安装这个包以及依赖项

pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter  # Filtering support

其次需要在setting.py中配置app

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myblog',
    # 这个就是REST依赖项
    'rest_framework'
]

然后就要写api了,先把评论的model放出来

class 评论(models.Model):
    评论者=models.CharField(max_length=20)
    评论日期=models.DateField(auto_now_add=True)
    评论时间=models.TimeField(auto_now_add=True)
    评论内容=models.TextField()
    对应文章=models.ForeignKey('myblog.文章内容',on_delete=models.CASCADE)

然后编写api.py

# api.py

# 引入model
from .models import 评论
# REST提供的序列化工具
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import api_view
Resonse

类似于HttpResponse,用来渲染文本内容,并根据内容决定返回给用户的数据类型

Response(data, status=None, template_name=None, headers=None, content_type=None)

# data:要渲染的数据,可以是python的基本数据类型
# status:状态码
# template_name:模板名称
# headers:头部信息
# content_type:内容类型的响应

因为Response只能渲染python基本数据类型,对于复杂的数据类型,需要serializers.ModelSerializer来序列化

# api.py

class PingLun(serializers.ModelSerializer):
    class Meta:
        depth = 1
        model = 评论
        fields = ('评论者','评论日期','评论内容')

然后就可以写url对应的回调函数了,可以不使用api_view修饰器,但需要自己写一个判断来判断请求的类型

@api_view(['GET'])
def showdata(request):
    id = request.GET['id']
    print(id)
    datas=评论.objects.filter(对应文章_id=id)
    PingLunData = PingLun(datas,many=True)
    return Response({'data':PingLunData.data})

这时候访问api就可以看到优雅的数据了,完了以后完善Vue,编写reqwest的内容

let vue = new Vue({
    el : "#app",
    data : {
        // 开始是一个空列表
        pinglun : [],
    },
    mounted(){
        console.log("卖个萌咋了!!!(>人<;)")
        this.getData()
    },
    computed : {

    },
    methods : {
        getData : function() {
            // 现在的this是window对象,等进入reqwest,this就是rewqest对象了,所以提前保存this
            let self = this
            // 只是为了获取当前文章的id
            let myurl = window.location.href
            let id = myurl.toString().split("/").pop()
          reqwest({
              url: '/blog/api/showpinglun/?format=json'
              , method: 'get'
              , data: [{name: 'id',value: id}]
              , success: function (data) {
                    self._data.pinglun = data.data
            }
        })
        },
    }
})

到目前,就可以使用Vue从数据库中获取数据并渲染到前端了,总结一下:

  1. 要用Vue渲染数据,数据就必须在data中,但我们又不能写死,必须从一个地方动态获取数据
  2. 这个地方就是api,django有一个模块REST专门用来建立api
  3. 要动态请求数据,需要用到一个框架 reqwest
  4. REST渲染数据用到了Resonse,但它只能渲染python基本数据,从Object.filter()得到的显然不是,因此还要序列化数据,这里用到了serializers
  5. 另外,还需要api_view这个装饰器判断请求类型

提交评论

思路:

  1. 使用POST请求
  2. 把表单内容交给api,api再保存到数据库

看着挺简单,但这里面有两个问题:

  1. Django要求所有POST请求进行CSRF验证,使用正常的表单我们可以添加{{csrf_token}},Django会自动在Cookies中添加csrf验证用的随机序列,用reqwest怎么办
  2. 一般情况下提交评论后评论会立刻显示在下面,怎么做

解决Ajax发送POST请求的CSRF问题

这里有两种思路

思路一:解决发现问题的人

这种思路简单粗暴,既然问题出在了csrf验证上,那就不让他进行验证就好了嘛,组织进行验证有两个简单的办法

使用装饰器

在要取消进行csrf验证的视图函数上添加修饰器@csrf_exempt

from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def demo(request):
    pass
赶尽杀绝法

第二种是狼人的做法,比较绝,直接从setting中删除csrf验证的依赖项

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 就是这个,删了就ok,但安全性嘛,就。。。。
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
思路二:釜底抽薪

思路一实现简单,一劳永逸,看似不错,但取消csrf验证会让网站处于很危险的境地,建议不要这样用,第二种方法就要优雅很多,首先要知道Django是怎样防御CSRF攻击的,CSRF,跨站请求伪造攻击,是攻击者利用用户登录保存的cookies伪装成用户进行非发操作的攻击方式,比如攻击者在某网站留下了一个付款的链接www.xxxx.com/shop?money=500;to=hark(注意,这个链接已经设计了用户验证,只有正确登录后才能付款,没登录直接访问这个链接会被重定向到登录界面),一个受害者在访问这个钓鱼链接之前正好访问过付款的那个网站,并登录留下了自己的cookies,这时候她再去访问那个钓鱼链接,浏览器就会检查本地有没有对应的cookies文件,正好有,系统就认为是他本人在付款,这就是一次csrf攻击,csrf的特点是攻击者并没有拿到受害人的cookies,针对这个特点,django的处理办法是在cookies中增加一个csrf_token字段,内容为随机序列,同时表单提交时也把这个序列作为表单的一项同表单数据一起提交给后端做验证,如果表单中的序列与cookies中的序列不一样,就定义为csrf攻击,在Debug模式下会抛出403错误。

根据这个,我们只要在Ajax的请求头中加上cookies中的那个字段就可以了嘛,其实如果不懂csrf,直接在浏览器开发者工具里对比我们的Ajax请求头和正常的POST请求头就会发现我们少了X-CSRFToken这个字段,获取本站cookies中的csrftoken字段,添加到请求头中就可以。其实对比发现我们还缺了一项,不写会报500错误,Content-Type

setRequestHeader必须写在open之后
// js获取cookies依赖下面的库
<script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>

// 获取cookies
let csrftoken = Cookies.get('csrftoken');

// 设置请求头
XHRObject.setRequestHeader("X-CSRFToken", csrftoken);

Ajax发送POST请求

<div id="app">
       <div class="alert alert-primary" role="alert">
            <p>评论<<</p>
             <hr />
           <div class="form-group">
                <label for="exampleFormControlInput1">评论者:</label>
                <input type="text" class="form-control" id="exampleFormControlInput1" placeholder="请输入你的姓名" name="评论者" maxlength="20" required="">
            </div>
            <div class="form-group">
                <label for="exampleFormControlTextarea1">有问题?不妨写下了...</label>
                <textarea class="form-control" name="评论内容" id="exampleFormControlTextarea1" rows="3"></textarea>
            </div>
        <hr>
           <button type="submit" name="评论提交"  οnclick="XMLDoc()"">提交评论</button>
    </div>
    function XMLDoc(){
        let XHRObject
        // 适配浏览器
        if(window.XMLHttpRequest){
            XHRObject = new XMLHttpRequest
        }else{
            XHRObject =new  ActiveXObject("Microsoft.XMLHTTP")
        }
        // 接收
        XHRObject.onreadystatechange = function () {
            if (XHRObject.status == 200 & XHRObject.readyState == 4) {
                }
        }

        // 获取文章id
        let url = window.location.href
        let id = url.toString().split("/").pop()
        // 获取csrftoken
        let csrftoken = Cookies.get('csrftoken');
        // 获取表单数据
        let name = document.getElementById('exampleFormControlInput1').value
        let neirong = document.getElementById('exampleFormControlTextarea1').value
        // 发送POST请求
        XHRObject.open("POST","/blog/api/postpinglun/?format=json",true)
        // 设置请求头
        XHRObject.setRequestHeader("X-CSRFToken", csrftoken);
        XHRObject.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        // 发送请求,只接受一个字符串,键值用=连接,多个键值对用&连接
        XHRObject.send('name='+name+'&neirong='+neirong+'&id='+id.toString())
        document.getElementById('exampleFormControlInput1').value = ""
        document.getElementById('exampleFormControlTextarea1').value = ""
    }

api保存数据到数据库

@api_view(['POST'])
def postdata(request):
    # 获取Ajax传来的表单信息
    name = request.POST['name']
    neirong = request.POST['neirong']
    id = request.POST['id']
    # 保存到数据库
    obj=评论(
        评论者 = name,
        评论日期 = datetime.datetime.now().strftime('%Y-%m-%d'),
        评论时间 = datetime.datetime.now().strftime('%H:%M:%S'),
        评论内容 = neirong,
        对应文章_id = id
    )
    obj.save()

提交数据时更新下方评论列表

要在提交时更新,就要绑定两个单击事件,一个是Ajax的,用来保存数据,另一个是Vue的,用来更新数据,这里可以直接调用之前的getData函数

  <button type="submit" name="评论提交"  onclick="XMLDoc()" @click="getData()">提交评论</button>
Logo

前往低代码交流专区

更多推荐