我們寫好后端的代碼,要把數據交給前端的展示的,這個數據以什么類型給前端呢?學到這里,我們已經知道這個數據最好是json字符串才行,因為網絡間的傳輸,只認字符串或者二進制,字符串就是我們的數據,二進制就是流媒體,比如圖片,視頻,音頻之類的
但是我們在后端經過邏輯處理得到的數據並不一定一開始就是個json字符串,所以就需要序列化下
補充:
序列化:將其他類型的數據轉為字符串
反序列化:將字符串轉回之前的數據類型(通常是字典類型)
在Python中,可用於序列化與反序列化的就是json和pickle模塊,但是為了與restful規范相映,我們就用json模塊(因為前后端分離后,不能保證以后會做成更多的樣子,所以選用通用的json字符串)
創建一個django實例:
我使用的是django2版本

建數據庫表:
from django.db import models
# 使用這個可以提前聲明表名,在使用外鍵約束時可以不用考慮表前后順序
__all__ = ['Book', 'Publisher', 'Author']
# Create your models here.
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name='圖書名稱')
CHOICES = ((1, 'python'), (2, 'GO'), (3, 'linux'))
category = models.IntegerField(choices=CHOICES, verbose_name='圖書類別')
pub_time = models.DateField(verbose_name='出版時間')
publisher = models.ForeignKey(to='Publisher', on_delete=None)
author = models.ManyToManyField(to='Author')
def __str__(self):
return self.title
class Meta:
# 自定義數據庫表名
verbose_name_plural = 'book'
db_table = verbose_name_plural
class Publisher(models.Model):
title = models.CharField(max_length=32, verbose_name='出版社名稱')
def __str__(self):
return self.title
class Meta:
verbose_name_plural = 'publish'
db_table = verbose_name_plural
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name='作者名稱')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'author'
db_table = verbose_name_plural
遷移數據庫后,添加數據:
出版社:

作者表:

圖書表:

為app demo1做路由分發,demo1下創建urls文件,基於CBV式(class-base-view,基於類的視圖)的指定url,當然你也可以使用FBV,基於函數的視圖函數,不過我建議你使用CBV的,后面你就會體會到了

定義視圖類對象:

這里注意使用json的dumps時使用ensure_ascii為false,這樣在dump序列化時才不會只轉為ascii式的數據,導致一些字段不能被ascii表示
啟動django,訪問/list/:

這樣沒問題了對吧?
但是如果我取得字段有時間呢?我加一個pub_time字段:
class BookView(View):
def get(self, request):
book_list = models.Book.objects.values('id', 'title','pub_time')
book_list = list(book_list)
ret = json.dumps(book_list, ensure_ascii=False)
return HttpResponse(ret)
再來訪問,報錯了

好的,這里就要用到 Jsonresponse
Jsonresponse
還是上面的代碼,用Jsonresponse返回:

里面加safe參數是因為我們從數據庫里取出來的是QuerySet類型,需要轉換成list更方便后面的處理。所以傳入safe,為什么傳入是因為它自己提示的如果是list則傳入safe
但是,由於我使用的pycharm,最開始是默認使用的django里帶的sqlite3,因為時間顯示會默認變成時間戳,在取出數據時會提示:Python int too large to convert to C long,意思就是int類型太長了,也就是說不能被識別為時間類型,所以我做了微調,把數據庫改成了mysql,添加的數據有點點不一樣,但是不影響
同樣的代碼,現在再訪問,可以了,但是還是有ascii碼的問題:

通過Jsonresponse的源碼可得,需要再傳入一個參數就行:
# coding:utf-8
from django.shortcuts import HttpResponse
from django.http import JsonResponse
from django.views import View
import json
# Create your views here.
from demo1 import models
class BookView(View):
def get(self, request):
book_list = models.Book.objects.values("id", "title", 'pub_time')
book_list = list(book_list)
# ret = json.dumps(book_list, ensure_ascii=False)
# print(book_list)
return JsonResponse(book_list, safe=False, json_dumps_params={'ensure_ascii': False})

如果你打開還是亂碼的,你可以考慮換成谷歌瀏覽器
但是,又來了問題,如果要取出版社publisher字段呢? 圖書表中我們只是關聯了出版社,此時它就是一個id啊,如果我們要出版社的名字,所以需要再處理:
class BookView(View):
def get(self, request):
book_list = models.Book.objects.values("id", "title", 'pub_time','publisher')
book_list = list(book_list)
ret = []
for field in book_list:
pub_id = field['publisher']
publish_obj = models.Publisher.objects.filter(id=pub_id).first()
field['publisher'] = {
'id':pub_id,
'title':publish_obj.title
}
ret.append(field)
return JsonResponse(ret, safe=False, json_dumps_params={'ensure_ascii': False})

數據是取出來了,但是你應該發現了一個問題,這個太不好維護了,在以后的開發中,肯定是數據很多,外鍵關聯也多,這樣一有一個外鍵關聯我們就要重新取處理一次,很麻煩對吧?而且還有choices字段,這個也要處理一下,反正以后遇到任何顯示不太符合習慣的都要做下處理,這樣是很費時間的,所以聰明的人想到了可以封裝一個類或者函數來處理,不過呢,有現成的,django已經給我們封好了一個serializers
django-serializers
代碼作適當處理:
# coding:utf-8
from django.shortcuts import HttpResponse
from django.http import JsonResponse
from django.views import View
import json
# Create your views here.
from demo1 import models
from django.core import serializers
class BookView(View):
def get(self,request):
book_list = models.Book.objects.all()
ret = serializers.serialize('json',book_list,ensure_ascii=False)
return HttpResponse(ret)
訪問,很直接的就出來了:

但是還是有點小問題,比如category字段無法顯示等的,所以這個serializer相對JSONResponse只是好一點點,還是要再處理,則用DRF
DRF序列化(get)
使用DRF那前提必須得裝djagnorestframework

下載完了之后,在django的配置文件settings.py里的app添加此app:
接下來就可以使用rest_framework里的工具模塊了
新建一個py文件,名字隨意,這里我在demo1的app根目錄下新建一個serializers,定義如下的類,對應models表的字段

代碼:
from rest_framework import serializers class PublishSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) CHOICES = ((1, 'python'), (2, 'GO'), (3, 'linux')) category = serializers.ChoiceField(choices=CHOICES,source='get_category_display') pub_time = serializers.DateField() publisher = PublishSerializer() author = AuthorSerializer(many=True)
視圖文件里:

代碼:
# coding:utf-8 from django.shortcuts import HttpResponse from django.http import JsonResponse from django.views import View import json # Create your views here. from demo1 import models from django.core import serializers from rest_framework.views import APIView from rest_framework.response import Response from demo1.serializers import BookSerializer class BookView(APIView): def get(self, request): book_list = models.Book.objects.all() ret = BookSerializer(book_list, many=True) return Response(ret.data)
注意視圖函數在序列化時用的many和在自定義序列化里用的many屬性,兩個注意區分,一個代表有多個字段值,一個代表多對多的外鍵約束
打開瀏覽器訪問:
這樣就很輕松的取出了相關的值,不需要我們自己再去手動處理了,很方便很實用,以后會經常用到這個序列化類
注:以上的web頁面是djangorestframework自己生成的,可以方便的做一些處理
既然有序列化,肯定也還有反序列化的
DRF反序列化(POST)
DRF的serializer不止可以序列化,當然還可以反序列化,這個就和django自己的form和modelform很類似了
對上面的序列化代碼進行微調:
序列化類:

代碼:
from rest_framework import serializers from demo1 import models class PublishSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=32) CHOICES = ((1, 'python'), (2, 'GO'), (3, 'linux')) category = serializers.ChoiceField(choices=CHOICES, source='get_category_display', read_only=True) w_category = serializers.ChoiceField(choices=CHOICES, write_only=True) pub_time = serializers.DateField() publisher = PublishSerializer(read_only=True) w_publish = serializers.IntegerField(write_only=True) author = AuthorSerializer(many=True, read_only=True) # 作者字段只需要傳入一個列表對象 w_author = serializers.ListField(write_only=True) def create(self, validated_data): book_obj = models.Book.objects.create(title=validated_data['title'], category=validated_data['w_category'], pub_time=validated_data['pub_time'], publisher_id=validated_data['w_publish']) book_obj.author.add(*validated_data['w_author']) return book_obj
視圖函數:

代碼:
# coding:utf-8 from django.shortcuts import HttpResponse from django.http import JsonResponse from django.views import View import json # Create your views here. from demo1 import models from django.core import serializers from rest_framework.views import APIView from rest_framework.response import Response from demo1.serializers import BookSerializer class BookView(APIView): def get(self, request): book_list = models.Book.objects.all() ret = BookSerializer(book_list, many=True) return Response(ret.data) def post(self, request): # post的數據不再是在request.POST里 # 而在request.data print(request.data) serialize = BookSerializer(data=request.data) if serialize.is_valid(): # save是存儲 serialize.save() return Response(serialize.validated_data) else: return Response(serialize.errors)
當再視圖里定義了post之后,頁面則多了下面的插入框提交數據:

提交后返回結果:

以上步驟就是反序列化
DRF的修改(put)
查看單條數據
url:

view:

訪問:

對單條數據修改
url不變
view,添加一個put方法
class BookEditView(APIView): def get(self, request, id): book_obj = models.Book.objects.filter(id=id).first() ret = BookSerializer(book_obj) return Response(ret.data) def put(self, request, id): book_obj = models.Book.objects.filter(id=id).first() # partial參數表示只對部分數據驗證 serialize = BookSerializer(book_obj, data=request.data, partial=True) print(book_obj) if serialize.is_valid(): # save是存儲 serialize.save() return Response(serialize.validated_data) else: return Response(serialize.errors)
序列化器,添加一個update方法
from rest_framework import serializers from demo1 import models class PublishSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) bookdata = { "title": "kali linux 入門到入獄", "w_category": 3, "pub_time": "2019-02-08", "w_publish": 2, "w_author": [1, 2, 4] } class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=32) CHOICES = ((1, 'python'), (2, 'GO'), (3, 'linux')) category = serializers.ChoiceField(choices=CHOICES, source='get_category_display', read_only=True) w_category = serializers.ChoiceField(choices=CHOICES, write_only=True) pub_time = serializers.DateField() publisher = PublishSerializer(read_only=True) w_publish = serializers.IntegerField(write_only=True) author = AuthorSerializer(many=True, read_only=True) # 作者字段只需要傳入一個列表對象 w_author = serializers.ListField(write_only=True) def create(self, validated_data): book_obj = models.Book.objects.create(title=validated_data['title'], category=validated_data['w_category'], pub_time=validated_data['pub_time'], publisher_id=validated_data['w_publish']) book_obj.author.add(*validated_data['w_author']) return book_obj def update(self, instance, validated_data): # 次數的instance就是圖書表數據庫對象 # 以下字段的key值也同樣必須使用上面的只寫的屬性key instance.title = validated_data.get('title',instance.title) instance.category = validated_data.get('w_category',instance.category) instance.pub_time = validated_data.get('pub_time',instance.pub_time) instance.publisher_id = validated_data.get('w_publish',instance.publisher_id) if validated_data.get('w_author'): instance.author.set(validated_data['w_author']) instance.save() return instance
注意以上的字段和前面的只讀或者只寫那些字段key值非常有關系,在修改時一定要一致
訪問頁面,並修改:
修改之前數據是這樣:

寫入以下修改,注意該怎么寫怎么寫,沒有空格縮進之類的,如果數據結尾了,也不要再加逗號分隔,結尾符之類的,不然報json的解析錯誤(我特么折騰了好半天才反應過來這里的問題):

修改字段如下:
{ "title":"linux就該這么學第四版", "pub_time":"2019-02-01", "w_publish":1, "w_author":[3] }
提交得:

以上返回的結果由這一段代碼所得,即已驗證后的數據——validated_data

刷新:

注意:在使用rest_framework之后,如果有報錯,很多時候rest_framework會把這個報錯接管並顯示在前端頁面,后端頁面如果后端邏輯沒錯的話是不會顯示的,導致你根本無法分析錯誤原因。原因由這一段代碼而來:

都說DRF的serializer跟form以及modelform很像,那么自然也有鈎子函數驗證了
局部驗證鈎子
例:對title字段做局部驗證:
在序列化類添加一個局部鈎子方法即可:方法名為validate_XXX(XXX為序列化類定義過的字段名):

代碼:
class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=32) CHOICES = ((1, 'python'), (2, 'GO'), (3, 'linux')) category = serializers.ChoiceField(choices=CHOICES, source='get_category_display', read_only=True) w_category = serializers.ChoiceField(choices=CHOICES, write_only=True) pub_time = serializers.DateField() publisher = PublishSerializer(read_only=True) w_publish = serializers.IntegerField(write_only=True) author = AuthorSerializer(many=True, read_only=True) # 作者字段只需要傳入一個列表對象 w_author = serializers.ListField(write_only=True) def create(self, validated_data): book_obj = models.Book.objects.create(title=validated_data['title'], category=validated_data['w_category'], pub_time=validated_data['pub_time'], publisher_id=validated_data['w_publish']) book_obj.author.add(*validated_data['w_author']) return book_obj def update(self, instance, validated_data): # 次數的instance就是圖書表數據庫對象 # 以下字段的key值也同樣必須使用上面的只寫的屬性key instance.title = validated_data.get('title', instance.title) instance.category = validated_data.get('w_category', instance.category) instance.pub_time = validated_data.get('pub_time', instance.pub_time) instance.publisher_id = validated_data.get('w_publish', instance.publisher_id) if validated_data.get('w_author'): instance.author.set(validated_data['w_author']) instance.save() return instance def validate_title(self, value): if 'python' not in value.lower(): raise serializers.ValidationError('修改失敗,內容必須包含python') return value
訪問頁面修改測試:

點擊put得:

完事兒了。有局部鈎子,自然也有全局鈎子
全局驗證鈎子
例:驗證修改的出版社和作者的id必須都為1,不然報錯:

代碼:
class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=32) CHOICES = ((1, 'python'), (2, 'GO'), (3, 'linux')) category = serializers.ChoiceField(choices=CHOICES, source='get_category_display', read_only=True) w_category = serializers.ChoiceField(choices=CHOICES, write_only=True) pub_time = serializers.DateField() publisher = PublishSerializer(read_only=True) w_publish = serializers.IntegerField(write_only=True) author = AuthorSerializer(many=True, read_only=True) # 作者字段只需要傳入一個列表對象 w_author = serializers.ListField(write_only=True) def create(self, validated_data): book_obj = models.Book.objects.create(title=validated_data['title'], category=validated_data['w_category'], pub_time=validated_data['pub_time'], publisher_id=validated_data['w_publish']) book_obj.author.add(*validated_data['w_author']) return book_obj def update(self, instance, validated_data): # 次數的instance就是圖書表數據庫對象 # 以下字段的key值也同樣必須使用上面的只寫的屬性key instance.title = validated_data.get('title', instance.title) instance.category = validated_data.get('w_category', instance.category) instance.pub_time = validated_data.get('pub_time', instance.pub_time) instance.publisher_id = validated_data.get('w_publish', instance.publisher_id) if validated_data.get('w_author'): instance.author.set(validated_data['w_author']) instance.save() return instance def validate_title(self, value): if 'python' not in value.lower(): raise serializers.ValidationError('修改失敗,內容必須包含python') return value def validate(self, attrs): if attrs['w_publish'] == 1 and attrs['w_category'] == 1: return attrs else: raise serializers.ValidationError('分類和作者id必須一樣')
訪問頁面提交測試:

提交得:

換成都為1的提交測試得:


自定義驗證鈎子
當然還可以自定義一個驗證鈎子:

代碼:
def my_validate(value): if '敏感信息' in value.lower(): raise serializers.ValidationError('不能含有敏感信息') else: return value class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=32, validators=[my_validate]) CHOICES = ((1, 'python'), (2, 'GO'), (3, 'linux')) category = serializers.ChoiceField(choices=CHOICES, source='get_category_display', read_only=True) w_category = serializers.ChoiceField(choices=CHOICES, write_only=True) pub_time = serializers.DateField() publisher = PublishSerializer(read_only=True) w_publish = serializers.IntegerField(write_only=True) author = AuthorSerializer(many=True, read_only=True) # 作者字段只需要傳入一個列表對象 w_author = serializers.ListField(write_only=True) def create(self, validated_data): book_obj = models.Book.objects.create(title=validated_data['title'], category=validated_data['w_category'], pub_time=validated_data['pub_time'], publisher_id=validated_data['w_publish']) book_obj.author.add(*validated_data['w_author']) return book_obj def update(self, instance, validated_data): # 次數的instance就是圖書表數據庫對象 # 以下字段的key值也同樣必須使用上面的只寫的屬性key instance.title = validated_data.get('title', instance.title) instance.category = validated_data.get('w_category', instance.category) instance.pub_time = validated_data.get('pub_time', instance.pub_time) instance.publisher_id = validated_data.get('w_publish', instance.publisher_id) if validated_data.get('w_author'): instance.author.set(validated_data['w_author']) instance.save() return instance def validate_title(self, value): if 'python' not in value.lower(): raise serializers.ValidationError('修改失敗,內容必須包含python') return value def validate(self, attrs): if attrs['w_publish'] == 1 and attrs['w_category'] == 1: return attrs else: raise serializers.ValidationError('出版社和作者id必須一樣')
訪問修改測試:


由於我們自定義的驗證鈎子和局部驗證鈎子剛好都沒通過,都會報錯,但是只走了我們自定義的驗證鈎子,說明自定義驗證鈎子比默認的局部鈎子優先級高
總算問題都解決了,但是感覺還是不太好,我們就這么點功能,序列化類都寫了那么多一坨,怎么優化呢?
ModelSerializer序列化
其他都不用變,重新定義一個序列化類,把剛才繼承serializer類的自定義序列化類改了其他的名字,現在再定義一個繼承Modelserializer的序列化類,名為BookSerializer
代碼:
from rest_framework import serializers from demo1 import models class BookSerializer(serializers.ModelSerializer): class Meta: model = models.Book fields = '__all__' depth = 1 # 表示外鍵查找層級
訪問頁面:

但是choices字段沒有顯示,做下稍微的調整
class BookSerializer(serializers.ModelSerializer): category = serializers.CharField(source="get_category_display") # 注意這里是CharField不是ChoicesField class Meta: model = models.Book fields = '__all__' depth = 1 # 表示外鍵查找層級
訪問頁面,已顯示choices字段:

顯示部分數據
由於上面的用的field = "__all__",所以默認會把所有字段顯示出來
部分顯示:
SerializerMethodField意思就是這個字段要通過方法獲取,下面的get_XXX是鈎子函數,使用了SerializerMethodField都得用鈎子函數來獲取,XXX字段名必須和定義的一致

訪問顯示,會靠前顯示我們自定義顯示的字段:

ModelSerializer反序列化
那么我們要對其進行修改怎么辦呢,想比Serializer,簡單很多:
在既有read_only又有write_only時,記得把depth字段注釋掉,不然報錯,extra_kwargs表示只讀訪問時不顯示,在修改(只寫write_only)時才有效

代碼:
class BookSerializer(serializers.ModelSerializer): category_display = serializers.SerializerMethodField(read_only=True) authors = serializers.SerializerMethodField(read_only=True) publish_info = serializers.SerializerMethodField(read_only=True) def get_category_display(self, obj): return obj.get_category_display() def get_publish_info(self, obj): publish_obj = obj.publisher return {"id": publish_obj.id, 'title': publish_obj.title} def get_authors(self, obj): # obj是Book對象 author_list = obj.author.all() return [{"id": author_obj.id, "name": author_obj.name} for author_obj in author_list] class Meta: model = models.Book fields = '__all__' # depth = 1 # 表示外鍵查找層級 extra_kwargs = { "category": {"write_only": True}, "publisher": {"write_only": True}, "author": {"write_only": True}, }
訪問查看:標注字段就是read_only時顯示的字段

修改測試一下:


好的,完事兒
DRF的刪除
這個根本上其實與DRF無關,就用django字典的View也可以事先,不過還是在DRF下刪除下,增刪改查才齊了
沒有任何特別的,就在BookEditView視圖類里加了下面這段:

訪問測試:
這個自動生成的頁面就是這樣,你寫了一個不同的類型的請求方法就會多個請求的按鈕

點擊delete,刪除成功

然后再在整個視圖組件里稍微的改下,就是把那些增刪改操作正確時返回,就返回下正常的數據,而不是返回驗證通過的數據:
例:

代碼:
# coding:utf-8 from django.shortcuts import HttpResponse from django.http import JsonResponse from django.views import View import json # Create your views here. from demo1 import models from django.core import serializers from rest_framework.views import APIView from rest_framework.response import Response from demo1.serializers import BookSerializer class BookView(APIView): def get(self, request): book_list = models.Book.objects.all() ret = BookSerializer(book_list, many=True) return Response(ret.data) def post(self, request): # post的數據不再是在request.POST里 # 而在request.data print(request.data) serialize = BookSerializer(data=request.data) if serialize.is_valid(): # save是存儲 serialize.save() return Response(serialize.data) else: return Response(serialize.errors) class BookEditView(APIView): def get(self, request, id): book_obj = models.Book.objects.filter(id=id).first() ret = BookSerializer(book_obj) return Response(ret.data) def put(self, request, id): book_obj = models.Book.objects.filter(id=id).first() # partial參數表示只對部分數據驗證 serialize = BookSerializer(book_obj, data=request.data, partial=True) if serialize.is_valid(): # print(serialize.validated_data) # save是存儲 serialize.save() return Response(serialize.data) else: return Response(serialize.errors) def delete(self, request, id): book_obj = models.Book.objects.filter(id=id).exists() if not book_obj: return Response('不存在id為%s的數據,請重試' % id) else: models.Book.objects.filter(id=id).delete() return Response("刪除成功")
序列化相關的還有很多,以上是主要的,更多的可以查看DRF的官方文檔:傳送門

好了,關於前后端分離,后端開發的序列化數據部分就到這
