Django接口开发

2,791 阅读3分钟

Django接口开发

一、前后端分离

  • 传统开发模式

url -->> 视图函数(逻辑判断、调用数据库、渲染HTML)-->>向浏览器返回HTML页面。 项目后台的商品列表页面已经使用传统开发模式实现了

但是呢,有时候我们需要将这些内容在移动端(手机APP)或者的设备上显示。那怎么办呢? 我们可以再编写一套后台逻辑给移动端使用。但是这样比较麻烦,工作量比较大。有没有更简单的方式呢?我们可以使用前后端分离开发模式

二、前后端分离模型

前后端分离模型指的是后端只负责返回数据,不再负责渲染页面。前端负责渲染数据

接下来就要探讨一下返回的数据类型,如果我们返回Python中的字典或者列表,但是前端人员或者移动端人员不一定会Python语法。我们可以返回一种前端人员、后台人员都会的一种数据类型,这样就可以顺利的完成开发了。一般在开发中我们返回JSON或者XML数据类型

三、JSON和XML

  • JSON介绍

    JSON(JavaScript Object Notation) : 是一种轻量级的数据交换格式,JSON数据格式类似于Python中的字典

  • XML介绍

    XML(Extensible Markup Language): 可扩展标记语言,很少企业使用XML进行数据传输,了解即可

  • JsonResponse

    form django.http import JsonResponse

    使用JsonResponse对象可以将Python中的字典或者其他数据类型转换为JSON数据

    # 路由
    urlpatterns += [
        # 查询所有商品
        path('api/goodses/', api_view.select_all_goods),
        
    # 视图
    def select_all_goods(request):
        """查询所有的商品"""
    
       # 查询所有商品得到的是QuerySet类型的对象
       goods_qs = Goods.objects.all()
       # 转成列表字典格式
       goods_list = [{"id": goods.id, "name": goods.name} for goods in goods_qs]
        # JsonResponse
       return JsonResponse(goods_list,safe=False,json_dumps_params=					{"ensure_ascii":False})
    
    

    safe=False可以使用列表,默认只转换字典格式 son_dumps_params={"ensure_ascii":False}:显示中文

四、AJAX

  • AJAX介绍

    AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步JavaScript和XML”。即使用JavaScript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)还有JSON数据

    AJAX还有一个最大的特点就是,当服务器响应时,不用刷新整个浏览器页面,而是可以局部刷新,这一特点给用户的感受是在不知不觉中完成请求和响应过程 应用场景:注册时信息校验(百度首页注册)

  • AJAX格式

    使用原生的JavaScript 使用AJAX 比较麻烦,我们采用 jQuery 实现 AJAX 请求。 1、语法:$.ajax({参数1:值1,参数2,值2})

    • 参数介绍
    参数 描述
    url 发送请求的地址
    type 请求方式("POST" 或 "GET"), 默认为 "GET"。注意:其它 HTTP 请求方法如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持
    data 发送到服务器的数据。格式 {key:value,key:value}
    error 请求失败时调用此函数
    success 请求成功后的回调函数,参数:由服务器返回数据
    dataType 预期服务器返回的数据类型。如果不指定,浏览器会智能判断

可用值: "xml": 返回 XML 文档,可用 jQuery 处理。 "html": 返回纯文本 HTML 信息; "script": 返回纯文本 JavaScript 代码。 "json": 返回 JSON 数据 |

  • 案例:

    1、前端需要做什么?

    • 以何种形式展示:web网页展示 发送参数 获取响应 局部刷新-修改DOM

    2、后端需要做什么?

    • 获取商品类型,根据商品类型查询商品 get请求
    # 视图
    def select_all_goods_by_goodstype(request):
        """查看更多,根据商品类型查询所有商品"""
        # 接收参数
        goodstype_id = request.GET.get("goodstype_id")
        # 查询数据
        goods_qs = Goods.objects.filter(goodstype_id=goodstype_id)
        # 转成列表字典格式
        goods_list = [{"id": goods.id, "name": goods.name,"price":goods.price,"picture":goods.picture.name,"unite":goods.unite} for goods in goods_qs]
        # JsonResponse
        return JsonResponse(goods_list,safe=False,json_dumps_params={"ensure_ascii":False})
    
    
    # 前端页面
    <script>
        // 根据url中的goodstype_id
        var url = window.location.href;
        var goodstype_id = url.split("?")[1].split("=")[1];
        //发送请求
        $.ajax({
            "url": "/api/more/",
            "method": "get",
            "data": {"goodstype_id": goodstype_id},
            "success": function (data) {
                {#console.log(data)#}
                {#console.log(typeof data)#}
                var divs = ""
                //遍历
                for(var i=0;i<data.length;i++){
                    var goods = data[i]
                    var id = goods["id"]
                    var name = goods["name"]
                    var picture = goods["picture"]
                    var price = goods["price"]
                    var unite = goods["unite"]
                    divs = divs+'<div class="col-xs-12 col-sm-3"><a href="detail.html" class="thumbnail"><img src="/static/ft/images/'+picture+'" class="img-responsive"></a><p class="text-center">'+name+'</p><p class="text-center myps"><span>¥'+price+'</span><span>'+price+'/'+unite+'</span><span><img src="/static/ft/images/carts.png"/></span></p></div>'
                }
                //修改dom,这里比较费劲
                $("#mybody").html(divs)
    
            },
            "error": function () {
                alert("error")
            }
        })
        $.ajax({
            "url": "/api/goodstype/",
            "method": "get",
            "data": {"goodstype_id": goodstype_id},
            "success": function (data) {
                $("#goodstype").html("全部分类 > "+data["name"])
            },
            "error": function () {
                alert("error")
            }
        })
    </script>
                
    

五、前端渲染

我们可以使用JS中的text、innerHtml、append 等方法,但是比较麻烦,我们可以使用Vue框架中的语法

  • Vue介绍

    Vue 是一套用于构建用户界面的JavaScript框架,前端使用Vue的目的就是把AJAX里面的数据绑定到前端

  • Vue基本语法

    • 下载引入

    • 1、第一个应用

      在Django的模版中使用Vue时,语法冲突,需要增加标签verbatim处理

      Vue.js的应用可以分为2个重要的组成部分,一个是视图(也就是HTML代码)另外一个是脚本

    • 
      
      
      # Vue使用[[]]语法
      "delimiters":["[[","]]"]
      
    • 2、v-bind 常用于绑定属性

          <a v-bind:href="url">百度</a>
      
    • v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染

    <div id="app">
        <div v-if="type === 'A'">
          A
        </div>
        <div v-else-if="type === 'B'">
          B
        </div>
        <div v-else-if="type === 'C'">
          C
        </div>
        <div v-else>
          Not A/B/C
        </div>
    </div>
        
    <script>
    new Vue({
      el: '#app',
      data: {
        type: 'C'
      }
    })
    </script>
    
    • v-for 可以通过一个对象的属性来迭代数据
    
    <div id="app">
      <ul>
        <li v-for="value in object">
        {{ value }}
        </li>
      </ul>
    </div>
     
    <script>
    new Vue({
      el: '#app',
      data: {
        object: {
          name: '菜鸟教程',
          url: 'http://www.runoob.com',
          slogan: '学的不仅是技术,更是梦想!'
        }
      }
    })
    </script>
    
    • 可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码
    <div id="app">
      <button v-on:click="counter += 1">增加 1</button>
      <p>这个按钮被点击了 {{ counter }} 次。</p>
    </div>
     
    <script>
    new Vue({
      el: '#app',
      data: {
        counter: 0
      }
    })
    </script>
    
    • Vue异步请求

      <div id="box">
      	<input type="button" @click="get()" value="点我异步获取数据(Get)">
      </div>
      <script type = "text/javascript">
      window.onload = function(){
      var vm = new Vue({
          el:'#box',
          data:{
              msg:'Hello World!',
          },
          methods:{
              get:function(){
                  //发送get请求
                  this.$http.get('/try/ajax/ajax_info.txt').then(function(res){
                      document.write(res.body);    
                  },function(){
                      console.log('请求失败处理');
                  });
              }
          }
      });
      }
      </script>
      
    • 案例:商品中 查看更多 功能

      1、导入包

      2、使用Vue,搭建环境

      <script>
          // 根据url中的goodstype_id
          var url = window.location.href;
          var goodstype_id = url.split("?")[1].split("=")[1];
          //创建VUE对象
          new Vue({
              el: "#mybody",
              delimiters: ["[[", "]]"],
              data: {
                  goods_list: [],
              },
              methods: {
                  select_info: function () {
                      this.$http.get("/api/more/?goodstype_id="+goodstype_id).then(function (content) {
                          //console.log(content)
                          //console.log(content.data)
                          this.goods_list = content.data
                      },function (data) {
                          alert("error")
                      })
                  }
              },
              mounted:function () {//相当于js onload表示窗体加载完毕
                  this.select_info()
              }
          })
      </script>
      

      3、使用Vue绑定

      <div class="panel-body" id="mybody">
                          <div class="col-xs-12 col-sm-3" v-for="goods in goods_list">
                              <a href="detail.html" class="thumbnail">
                                  <img v-bind:src="'/static/ft/images/'+[[goods.picture]]" class="img-responsive">
                              </a>
                              <p class="text-center">[[goods.name]]</p>
      
                              <p class="text-center myps"><span>¥[[goods.price]]</span><span>[[goods.price]]/[[goods.unite]]</span><span><img
                                      src="/static/ft/images/carts.png"/></span></p>
                          </div>
                      </div>
      

六、REST

REST(Representational State Transfer的简称,中文翻译为“表现层状态转移”)与技术无关, 是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。基于这个风格

设计的软件可以更简洁,更有层次,更易于实现缓存等机制

满足这些约束条件和原则的应用程序或设计就是 RESTful

REST需要遵循如下10条规则:

1、协议

API与用户的通信协议,总是使用HTTPS协议

2、域名

在域名上进行区分,例如

子域名方式:

www.ujiuye.com 所有人都知道这是访问网站

api.ujiuye.com(cts.ujiuye.com) 看到这个网站就就知道返回json、xml数据

Url上区分:

www.ujiuye.com/

www.ujiuye.com/api/ (添加一个api 目录,让人一看到就知道是一个接口)

3、版本

因为项目存在着版本迭代更新,因此建议在url上添加版本

www.ujiuye.com/api/v1/

www.example.com/app/1.0/foo

v1、1.0: 就是代表第一版

4、路径

网络上任何东西都是资源,均使用名词表示(可复数)

通俗来说,URL不应该使用动作来描述。例如,下面是一些不符合统一接口 要求的URI:

GET:/getUser/1、

POST:/createUser、

PUT:/updateUser/1 、

DELETE:/deleteUser/1

建议使用 /user/1

5、HTTP动词

对于资源的具体操作类型,由HTTP动词表示

常用的HTTP动词有下面四个(括号里是对应的SQL命令)

GET(SELECT):从服务器取出资源(一项或多项)

POST(CREATE):在服务器新建一个资源

PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)

DELETE(DELETE):从服务器删除资源

还有三个不常用的HTTP动词

PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)

HEAD:获取资源的元数据

OPTIONS:获取信息,关于资源的哪些属性是客户端可以访问改变的

下面是一些例子

GET /zoos:列出所有动物园

POST /zoos:新建一个动物园(上传文件)

GET /zoos/ID:获取某个指定动物园的信息

PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)

PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)

DELETE /zoos/ID:删除某个动物园

GET /zoos/ID/animals:列出某个指定动物园的所有动物

DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

6、过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数

?limit=10:指定返回记录的数量

?offset=10:指定返回记录的开始位置

?page=2&per_page=100:指定第几页,以及每页的记录数

?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序

?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复 比如:

GET /zoos/ID/animals

GET /animals?zoo_id=ID

二者含义是相同的

7、状态码

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

200 OK - [GET]:服务器成功返回用户请求的数据

201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功

202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)

204 NO CONTENT - [DELETE]:用户删除数据成功

400 INVALID REQUEST

[POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作

401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)

403 Forbidden - [*]

表示用户得到授权(与401错误相对),但是访问是被禁止的

404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的

406 Not Acceptable

[GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)

410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的

422 Unprocesable entity - [POST/PUT/PATCH]

当创建一个对象时,发生一个验证错误

500 INTERNAL SERVER ERROR -

[*]:服务器发生错误,用户将无法判断发出的请求是否成功

8、错误处理

如果状态码是4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可

{error: "Invalid API key"}

9、返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范

GET /collection:返回资源对象的列表(数组)

GET /collection/resource:返回单个资源对象

POST /collection:返回新生成的资源对象

PUT /collection/resource:返回完整的资源对象

PATCH /collection/resource:返回完整的资源对象

DELETE /collection/resource:返回一个空文档

10、超媒体

RESTful API最好做到Hypermedia(即返回结果中提供链接,连向其他API方法),使得用户不查文档,也知道下一步应该做什么

比如,Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表

{ "current_user_url": "api.github.com/user", "authorizations_url": "api.github.com/authorizati…", // ... }

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果

{ "message": "Requires authentication", "documentation_url": "developer.github.com/v3" }

上面代码表示,服务器给出了提示信息,以及文档的网址

11、其他

服务器返回的数据格式,应该尽量使用JSON,避免使用XML

六、FBV和CBV

Django中关于视图有两种写法:基于函数的视图(FBV)和基于类的视图(CBV) function base view 函数式class base view 类式

  • FBV

    函数至少有一个参数,一般名字是request,表示当前请求对象

    http常见的请求方式,rest中要求请求方式与要完成的动作(增删改查)对应 post:增 delete:删 put/patch:改(全部改,局部改) get:查

  • CBV

    视图类必须直接或者间接继承django.views.View类,定义方法表示接受对应的请求。 方法中request表示请求对象,*args和**kwargs用来接收

  • CSRF拦截

    form表单默认只支持get和post两种。可以使用里AJAX发送post、delete、put、get,但是需要注意Django的CSRF,get不会被拦截,其他3个都会被拦截。可以使用postman工具测试 这里可以用csrf_exempt装饰器,去掉CSRF拦截

    视图类先调用dispatch方法,然后根据不同的请求,调用对应的方法。再次使用postman测试,通过

八、Django REST Framework 框架

自己写REST接口太复杂,因为规范很多,Django拥有丰富的插件,有一个关于REST的插件,使用起来比较方便,文档:www.django-rest-framework.org/

  • 安装REST框架

    pip install djangorestframework

    1、 注册app

  • 基本使用

    REST中的序列化类可以进行数据格式的转换,例如将Python中的字典转换成JSON数据格式。 json序列化:实体类(或字典)与json格式字符串转换

    1、 创建Serializer类

    在应用当中创建serializers文件用来编写序列化类 需要继承 ModelSerializer或HyperlinkedModelSerializer,后者会生成url路由

    from rest_framework import serializers
    from store.models import *
    
    
    class GoodsSerializer(serializers.ModelSerializer):
        class Meta:
            model = Goods
            fields = "__all__"
    

    Serializer内部类Meta属性解释:

    (1)model:对应的model类

    (2)fields:model类要序列化的属性 “all” 表示所有属性

    (3)depth:关联对象序列化的深度,默认只序列化关联对象的id,设置为1后序列化关联对象所有filelds属性

    2、创建视图

    from rest_framework import viewsets
    from api.serializes import *
    
    class GoodsViewSet(viewsets.ModelViewSet):
        queryset = Goods.objects.all()
        serializer_class = GoodsSerializer
    

    这里指定将queryset数据使用serializer_class序列化为json数据

    3、路由指出

    from rest_framework.routers import DefaultRouter
    
    # 创建DRF相关的路由
    router = DefaultRouter()
    router.register("rest/goods/", api_view.GoodsViewSet)
    urlpatterns += router.urls
    

    4、简单的分页 接口直接返回所有数据是不合适的,并且数据量太大的时候容易发生问题,所以可以使用分页来解决这个问题。DRF提供了快速的分页策略,只需要在settings当中进行配置就可以了

    # REST分页
    REST_FRAMEWORK = {
        "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
        "PAGE_SIZE": 10
    }
    
  • 视图类

    1、继承APIView类

    APIView 对 Django 中的django.views.View 类进行了进一步封装,功能更加强大,功能更加强大

    class SelectGoodsByGoodsTypeId(APIView):
        """根据商品类型查询所有商品:DRF,不能使用DRF的分页"""
    
        def get(self, request, *args, **kwargs):
            """接收GET请求"""
            # 接收参数
            goodstype_id = request.GET.get("goodstype_id")
            # 查询数据
            goods_qs = Goods.objects.filter(goodstype_id=goodstype_id).order_by("-id")
            # 序列化
            serializer = GoodSerializer(goods_qs, many=True)
            # JsonResponse
            return JsonResponse(serializer.data, safe=False)
    

    2、路由

    # 根据商品类型id查看商品
    path('api/more2/',api_view.SellerGoodsByGoodsTypeId.as_view())
    

​ 3、使用mixins类

​ 使用REST框架的mixins类,可以提高代码的重用率,使用OPP中的继承

​ class CreateModelMixin(object):增加

​ class ListModelMixin(object):展示数据列表

​ class RetrieveModelMixin(object):展示单条数据

​ class UpdateModelMixin(object):更新单条数据

​ class DestroyModelMixin(object):删除单条数据

​ 视图:

class GoodsView(generics.GenericAPIView,
                mixins.ListModelMixin,
                mixins.DestroyModelMixin,
                mixins.RetrieveModelMixin,
                mixins.UpdateModelMixin,
                mixins.CreateModelMixin):
    """根据商品类型查询所有商品:DRF。推荐使用"""

    # 序列化类
    serializer_class = GoodSerializer

    def get_queryset(self):
        """查询结果集"""
        goodstype_id = self.request.GET.get("goodstype_id")
        if goodstype_id:
            goods_qs = Goods.objects.filter(goodstype_id=goodstype_id).order_by("-id")
        else:
            goods_qs = Goods.objects.all().order_by("-id")
        return goods_qs

    def get(self, request, *args, **kwargs):
        """get请求"""
        # 获取参数
        pk = kwargs.get("pk")
        # 判断
        if pk:  # 查询单个
            return self.retrieve(request, *args, **kwargs)
        else:  # 查询集合
            return self.list(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        """delete请求"""
        # 删除
        return self.destroy(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        """put请求"""
        # 修改
        return self.update(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        """put请求"""
        # 新增
        return self.create(request, *args, **kwargs)

    def get_serializer_context(self):
        return {"view": self}

路由:

    #查询集合
    path('api/goods/', api_view.GoodsView.as_view()),

    #对应的删除,修改,更新,查单个
    # api/goods/100  delete
    # api/goods/100  update
    # api/goods/100  get
    re_path('api/goods/(?P<pk>\d+)$', api_view.GoodsView.as_view()),

4、通用视图类

第2视图类的简化

使用mixin类,我们重写了视图,使用的代码比以前略少,但我们可以更进一步。REST框架提供了一组已经混合的通用视图,我们可以使用它来进一步减少我们的views.py模块

视图:

class GoodsView(generics.ListAPIView):
    """根据商品类型查询所有商品:DRF"""

    # 序列化类
    serializer_class = GoodSerializer

    def get_queryset(self):
        """查询结果集"""
        goodstype_id = self.request.GET.get("goodstype_id")
        if goodstype_id:
            goods_qs = Goods.objects.filter(goodstype_id=goodstype_id).order_by("-id")
        else:
            goods_qs = Goods.objects.all().order_by("-id")
        return goods_qs

    def get_serializer_context(self):
        return {"view": self}