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/
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.urls4、简单的分页 接口直接返回所有数据是不合适的,并且数据量太大的时候容易发生问题,所以可以使用分页来解决这个问题。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}