rest-framework 框架的基本组件
快速实例
序列化
创建一个序列化类
简单使用
开发 Web API 的第一件事是为 Web API 提供一种将代码片段实例序列化和反序列化为诸如 json 之类的表示形式的方式。我们可以通过声明与Django forms 非常相似的序列化器(serializers)来实现。
models 部分:
from django.db import models # Create your models here. class Book(models.Model): title=models.CharField(max_length=32) price=models.IntegerField() pub_date=models.DateField() publish=models.ForeignKey("Publish") authors=models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name=models.CharField(max_length=32) email=models.EmailField() def __str__(self): return self.name class Author(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() def __str__(self): return self.name
views 部分:
from django.shortcuts import render, HttpResponse from django.core import serializers from django.views import View from .models import * import json from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers # Create your views here. # Serializer是从rest_framework中的类 class BookSerializers(serializers.Serializer): title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() # publish 是一对多的外键,如果不加source="publish.pk",则使用的是model.py中表的__str__ publish = serializers.CharField(source="publish.pk") # authors 是ManyToManyField类型,可以按照publish的方式来写,但是结果看起来不清晰 # authors = serializers.CharField(source="authors.all") # "authors": "<QuerySet [<Author: xh>, <Author: xh>]>" # 将authors 按照下面的方式写 authors = serializers.SerializerMethodField() # def get_authors(self, obj): # temp = [] # for author in obj.authors.all(): # temp.append(author.name) # return temp ''' 显示的结果是 [ { "title": "php", "price": 13, "pub_date": "2018-03-02", "publish": "3", "authors": ["xh","xh"] }, { "title": "python", "price": 24, "pub_date": "2018-04-09", "publish": "2", "authors": [ "xh","xm"] } ] ''' # 也可以进行自定制显示样式 def get_authors(self, obj): temp = [] for author in obj.authors.all(): temp.append({"pk": author.pk, "name": author.name}) return temp ''' 显示的author是 [ { "title": "php", "price": 13, "pub_date": "2018-03-02", "publish": "3", "authors": [{"pk": 2, "name": "xh"},{"pk": 2,"name": "xh"}] }, { "title": "python", "price": 24, "pub_date": "2018-04-09", "publish": "2", "authors": [{"pk": 2,"name": "xh"},{"pk": 1,"name": "xm"}] } ] ''' class BookViewSet(APIView): def get(self, request, *args, **kwargs): book_list = Book.objects.all() # 序列化方式一: # book_list = list(Book.objects.all().values("title", "price")) # return HttpResponse(json.dumps(book_list)) # 序列化方式二: # temp = [] # for 循环book_list,得到的每一个book,都是一个book对象 # for book in book_list: # temp.append({ # "title": book.title, # "price": book.price, # "pub_data": book.pub_date # }) # return HttpResponse(json.dumps(temp)) # 序列化方式三: # temp = serializers.serialize("json", book_list) # return HttpResponse(temp) # 序列化方式四:这个时候就不能继承View,要继承的是APIView # 将book_list 转换成json数据 [{}, {}, {}] bs = BookSerializers(book_list, many=True) return Response(bs.data)
ModelSerializer
# 上面写的 BookSerializers 换做以下类似于 ModelForm 的写法,更简洁 # 这里 BookSerializers 继承的是 serializers.ModelSerializer class BookSerializers(serializers.ModelSerializer): class Meta: model = Book fields = "__all__" # 当序列化类 Meta 中定义了depth 时,这个序列化类中引用字段(外键)则自动变为只读, # 所以在进行更新或者创建的操作的时候不能使用此序列化类 # depth = 1
提交POST请求
def post(self, request, *args, **kwargs): # 得到用户添加的数据,其中request在APIView中的def dispatch中通过request = self.initialize_request(request, *args, **kwargs)进行了重新定义, # 现在使用的request = self.request._request bs = BookSerializers(data=request.data, many=False) if bs.is_valid(): # 对数据bs进行验证 bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
重写 save 中的 create 方法
class BookSerializers(serializers.ModelSerializer): class Meta: model=Book fields="__all__" # exclude = ['authors',] # depth=1 def create(self, validated_data): authors = validated_data.pop('authors') obj = Book.objects.create(**validated_data) obj.authors.add(*authors) return obj
或者是用之前的方法,将代码改成以下形式:
class BookSerializers(serializers.ModelSerializer): class Meta: model = Book fields = "__all__" # 当序列化类 Meta 中定义了depth 时,这个序列化类中引用字段(外键)则自动变为只读, # 所以在进行更新或者创建的操作的时候不能使用此序列化类 # depth = 1 # 自定义authors字段的显示格式 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for author in obj.authors.all(): temp.append({"pk": author.pk, "name": author.name}) return temp
单条数据的 get 和 put 请求
urls.py 文件中添加一条url:
url(r'^books/(?P<pk>\d+)/$', views.BookDetailViewSet.as_view(), name="book_detail"),
view.py 文件中:
class BookDetailViewSet(APIView): def get(self, request,pk, *args, **kwargs): book_list = Book.objects.filter(pk=pk) # 实例化一个带有数据的 BookSerializers 对象 bs = BookSerializers(book_list) return Response(bs.data) def post(self, request, pk, *args, **kwargs): book_list = Book.objects.filter(pk=pk) bs = BookSerializers(book_list, data=request.data) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
超链接 API:Hyperlinked
class BookSerializers(serializers.ModelSerializer): publish = serializers.HyperlinkedIdentityField( view_name="book_detail", # 是urls.py中的name的值 lookup_field="publish_id", # 在页面展示时的格式 lookup_url_kwarg="pk" ) class Meta: model = Book fields = "__all__" ''' [ { "id": 1, "publish": "http://127.0.0.1:8001/books/3/", "title": "php", "price": 13, "pub_date": "2018-03-02", "authors": [2] }, { "id": 2, "publish": "http://127.0.0.1:8001/books/2/", "title": "python", "price": 24, "pub_date": "2018-04-09", "authors": [2,1] } ] '''
视图三部曲
使用混合(mixins)
上一节的视图部分
from rest_framework import serializers from rest_framework.views import APIView from rest_framework.response import Response from .models import * from django.shortcuts import HttpResponse from django.core import serializers class BookSerializers(serializers.ModelSerializer): publish = serializers.HyperlinkedIdentityField( view_name="book_detail", # 是urls.py中的name的值 lookup_field="publish_id", # 在页面展示时的格式 lookup_url_kwarg="pk" ) class Meta: model = Book fields = "__all__" class PublishSerializers(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__" class BookViewSet(APIView): def get(self, request, *args, **kwargs): book_list = Book.objects.all()
# many = True 是可以同时序列化一个Queryset对象 bs = BookSerializers(book_list, many=True, context={'request': request}) return Response(bs.data) def post(self, request, *args, **kwargs):
# request.data传的其实是一个Unicode字符串 bs = BookSerializers(data=request.data, many=False, context={'request': request}) if bs.is_valid(): # 对数据bs进行验证 bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class BookDetailViewSet(APIView): def get(self, request,pk, *args, **kwargs): book_list = Book.objects.filter(pk=pk) # 实例化一个带有数据的 BookSerializers 对象 bs = BookSerializers(book_list) return Response(bs.data) def post(self, request, pk, *args, **kwargs): book_list = Book.objects.filter(pk=pk) bs = BookSerializers(book_list, data=request.data, context={'request': request}) if bs.is_valid(): bs.save() # save 内部做了一个updata 操作 return Response(bs.data) else: return HttpResponse(bs.errors) class PublishViewSet(APIView): def get(self, request, *args, **kwargs): publish_list = Publish.objects.all() bs = PublishSerializers(publish_list, many=True, context={'request': request}) return Response(bs.data) def post(self, request, *args, **kwargs): bs = PublishSerializers(data=request.data, many=False, context={'request': request}) if bs.is_valid(): # 对数据bs进行验证 bs.save() # save 的内部做了一个create操作 return Response(bs.data) else: return HttpResponse(bs.errors) class PublishDetailViewSet(APIView): def get(self, request,pk, *args, **kwargs): publish_list = Publish.objects.filter(pk=pk) bs = PublishSerializers(publish_list) return Response(bs.data) def post(self, request, pk, *args, **kwargs): publish_list = Publish.objects.filter(pk=pk) bs = PublishSerializers(publish_list, data=request.data, context={'request': request}) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
mixin类编写视图
from rest_framework import mixins from rest_framework import generics from api.service.serializers import BookSerializers, PublishSerializers class BookViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView ): queryset = Book.objects.all() serializer_class = BookSerializers def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class BookDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView ): queryset = Book.objects.all() serializer_class = BookSerializers def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.delete(request, *args, **kwargs) class PublishViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView ): queryset = Publish.objects.all() serializer_class = PublishSerializers def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class PublishDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView ): queryset = Publish.objects.all() serializer_class = PublishSerializers def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.delete(request, *args, **kwargs)
发现代码的重复还是很严重
使用通用的基于类的视图
通过使用mixin类,我们使用更少的代码重写了这些视图,但我们还可以更进一步来简化代码。
REST 框架提供了一组已经混合好的通用视图,可以使用它来简化 views.py 模块。
class BookSerializers(serializers.ModelSerializer): publish = serializers.HyperlinkedIdentityField( view_name="book_detail", # 是urls.py中的name的值 lookup_field="publish_id", # 在页面展示时的格式 lookup_url_kwarg="pk" ) class Meta: model = Book fields = "__all__" class PublishSerializers(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__" class BookView(generics.ListCreateAPIView): queryset = Book.objects.all() serializer_class = BookSerializers class BookDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = BookSerializers class PublishView(generics.ListCreateAPIView): queryset = Publish.objects.all() serializer_class = PublishSerializers class PublishDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = Publish.objects.all() serializer_class = PublishSerializers
viewsrts.Model.ModelViewSet
urls.py 部分:
from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', views.BookView.as_view({"get": "list", "post": "create"})), url(r'^publishes/$', views.PublishView.as_view({"get": "list", "post": "create"})), url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view({'get': 'retrieve','put': 'update','patch': 'partial_update','delete': 'destroy'}), name="book_detail"), url(r'^publishes/(?P<pk>\d+)/$', views.PublishDetailView.as_view({'get': 'retrieve','put': 'update','patch': 'partial_update','delete': 'destroy'}), name="book_detail"), ]
views.py 文件中
# 这一部分被移到了 api.service.serializers文件中了 from rest_framework import serializers from ..models import *
# Book表的序列化组件 class BookSerializers(serializers.ModelSerializer): publish = serializers.HyperlinkedIdentityField( view_name="book_detail", # 是urls.py中的name的值 lookup_field="publish_id", # 在页面展示时的格式 lookup_url_kwarg="pk" ) class Meta: model = Book fields = "__all__"
# Publish表的序列化组件 class PublishSerializers(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__" # 这一部分是在 views.py 文件中的 rom rest_framework import viewsets class BookView(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializers class BookDetailView(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializers class PublishView(viewsets.ModelViewSet): queryset = Publish.objects.all() serializer_class = PublishSerializers class PublishDetailView(viewsets.ModelViewSet): queryset = Publish.objects.all() serializer_class = PublishSerializers
认证与权限组件
认证组件
局部视图认证
这时候发现原来的表不够用了,将原来的model.py文件中添加下面的两张表:
from django.db import models class User(models.Model): username = models.CharField(max_length=32) pwd = models.CharField(max_length=32) tokentyppe = models.IntegerField(choices=((1, "大众会员"),(2, "白银会员"), (3, "黄金会员"), (3, "钻石会员")), default=1) def __str__(self): return self.username class UserToken(models.Model): user = models.OneToOneField("User") token = models.CharField(max_length=128)
在 api.service.auth.py文件中:
from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from ..models import * class Authentication(BaseAuthentication): def authenticate(self, request): token = request._request.GET.get("token") token_obj = UserToken.objects.filter(token=token).first() # 后面 .first()得到的是一个obj对象 # 要进行认证,则需要通过判断是否有 token 为标准 if not token_obj: return exceptions.AuthenticationFailed("验证失败") return (token_obj.user, token_obj)
在views.py 文件中:
from rest_framework import viewsets from api.service.auth import * from django.http import JsonResponse def get_random_str(user): import hashlib, time # 将用户登录时的当前时间转换成str类型,生成一个随机字符串 ctime = str(time.time()) # 将用户名转换成utf8编码的bytes类型,并进行md5加密 md5 = hashlib.md5(bytes(user, encoding="utf8")) md5.update(bytes(ctime, encoding="utf8")) return md5.hexdigest() class LoginView(APIView): # 这里是在局部进行认证 # 这里的authentication_classes是来自于APIView中的源码,名字不可随意更改, # 在其后的列表中加入自己写的Authentication类,如果没有自己写Authentication类,就会默认走父类自己的DEFAULT_AUTHENTICATION_CLASSES # authentication_classes = [MyAuthentication, ] def post(self, request, *args, **kwargs): # 定义返回值,当用户登录成功时code=100,当用户登录错误的时候返回错误提示msg res = {"code":100, "msg": None} # request.data 得到的是原生数据 user = request.data.get("username") # request在源码中又复写了POST方法,所以request.POST == request._request.POST pwd = request.data.get("pwd") user_obj = User.objects.filter(username=user, pwd=pwd).first() # 后面加上.first()得到的是一个obj对象 if not user: res["code"] = 110 res["msg"] = "用户名或密码错误" else: token = get_random_str(user_obj.username) # 在第一次登录的时候会自动创建一条token记录,如果不是第一次登录,则会更新原来的token记录 user_token_obj = UserToken.objects.update_or_create(user=user_obj, defaults={"token": token}) res["token"] = token res["msg"] = "登录成功" print(res["msg"]) return JsonResponse(res)