一. 前言
在开发REST API接口时,视图中做的最主要有三件事:
- 将请求的数据(如JSON格式)转换为模型类对象
- 操作数据库
- 将模型类对象转换为响应的数据(如JSON格式)
序列化:
将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等),例如将Django中的模型类对象装换为JSON字符串,这个转换过程我们称为序列化。
简单的一句话理解就是将数据转化为JSON格式返回给前端
反序列化:
反之,将其他格式(字典、JSON、XML等)转换为程序中的数据,例如将JSON字符串转换为Django中的模型类对象,这个过程我们称为反序列化。
总结
在开发REST API时,视图中要频繁的进行序列化与反序列化的编写。
在开发REST API接口时,我们在视图中需要做的最核心的事是:
- 将数据库数据序列化为前端所需要的格式,并返回;
- 将前端发送的数据反序列化为模型类对象,并保存到数据库中。
二. 环境安装配置
1. 安装DRF
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple djangorestframework
2. 添加rest_framework应用
INSTALLED_APPS = [...'rest_framework',
]
接下来就可以使用DRF进行开发了。
普通的序列化器这里就不介绍了,下面介绍的时进阶版的模型类序列化器-ModelSerializer
此项目使用的时前后端不分离的开发模式,本质上给前端传递数据是一样的
三. 序列化与反序列化
模型表models.py
from django.contrib.auth.models import User
from django.db import modelsfrom apps.file.models import TeamDataStructureFile
from utils.base_models import BaseModelclass DataStructure(BaseModel):CATEGORY_CHOICES = [('Knowledge Category', 'Knowledge Category'),('Knowledge Category1', 'Knowledge Category1'),]TEAM_CHOICES = [('sys-tech', 'sys-tech'),('data structure', 'data structure'),]title = models.CharField(max_length=200, verbose_name='主题', blank=False)owner = models.CharField(max_length=50,blank=True,verbose_name='申请人ID')full_name = models.CharField(max_length=50,blank=False,verbose_name='申请人')knowledge_category = models.CharField(max_length=50,blank=False,choices=CATEGORY_CHOICES,verbose_name='知识类别')team = models.CharField(max_length=50,blank=False,choices=TEAM_CHOICES,verbose_name='团队')description = models.TextField(null=True, blank=True, verbose_name='描述')# 添加user, file复合主键# 一对多user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)# 多对多attachments = models.ManyToManyField(TeamDataStructureFile, blank=True)class Meta:ordering = ['-create_time']def __str__(self):return self.title
序列化器serializers.py
from rest_framework.serializers import ModelSerializerfrom apps.file.models import TeamDataStructureFile
from apps.file.serializers import TeamDataStructureFileField
from teams.models import DataStructureclass DataStructureSerializer(ModelSerializer):"""序列化与反序列化数据时可以使用"""class Meta:model = DataStructurefields = '__all__'class EditDataStructureSerializer(ModelSerializer):"""编辑数据反序列化器"""class Meta:model = DataStructureexclude = ('attachments',) # 若编辑文件接口提交时文件为空,此时不需要序列化此字段class TeamDataStructureQuerySerializer(ModelSerializer):"""查询所有数据的序列化器"""attachments = TeamDataStructureFileField(queryset=TeamDataStructureFile.objects.all(), many=True)class Meta:model = DataStructurefields = '__all__'
1.添加数据
视图函数views.py
class AddDataStructureView(LoginRequiredJSONMixin, APIView):def get(self, request):ds = DataStructure()knowledge_category = [i[1] for i in ds.CATEGORY_CHOICES]teams = [i[1] for i in ds.TEAM_CHOICES]context = {'add_knowledge_category': knowledge_category,'add_teams': teams,}return render(request, 'teams/data_structure/add_data_structure.html', context)@transaction.atomicdef post(self, request):"""反序列化一条数据,存入数据库"""# print(request.data)serializer = DataStructureSerializer(data=request.data)if serializer.is_valid():# serializer.ds = serializer.save()# handle filefiles_obj = request.FILES.getlist('uploadFile')if files_obj:handle_files(request, files_obj, ds, TeamFileSerializer)return api_success('信息保存成功!Data loading')return api_bad_request('表单数据输入有误,认证失败,数据无法保存!')
2.查询/编辑/删除数据
class DataStructureDetailView(LoginRequiredJSONMixin, APIView):def get(self, request, id):"""序列化器数据,并返回给前端"""ds = get_object_or_404(DataStructure, pk=id)s = DataStructureSerializer(ds)context = s.datacontext['teams'] = [i[1] for i in ds.TEAM_CHOICES if i[1] != s.data['team']]context['knowledge_categories'] = [i[1] for i in ds.CATEGORY_CHOICES if i[1] !=s.data['knowledge_category']]attachments = ds.attachments.all()initialPreviewData = []try:for file in attachments:initialPreviewData.append({'file_id': file.id,'file_url': SERVER_URL + '/' + file.file.url,'file_type': file.suffix.lower(),'file_name': file.filename,'file_size': file.file.size})except Exception as e:logger.info('get DataStructureDetailView error:{}'.format(e))context['initialPreviewData'] = initialPreviewDatacontext['user'] = ds.user# print(context)return render(request, 'teams/data_structure/edit_data_structure.html', context)@transaction.atomicdef put(self, request, id):""""反序列化数据,存入数据库"""# print(request.data)ds = get_object_or_404(DataStructure, pk=id)old_ds = copy.copy(ds)# 此处重新定义了序列化器是因为文件传过来的时候为空,所以需要重新定义新的序列化器,先处理数据,再处理文件,exclude attachments字段可使该字段在处理数据时被忽略,最后在单独处理该字段s = EditDataStructureSerializer(instance=ds, data=request.data)if s.is_valid():new_ds = s.save()# handle filefiles_obj = request.FILES.getlist('uploadFile')if files_obj:handle_files(request, files_obj, new_ds, TeamFileSerializer)# 变更差异信息# old_ds_dic = model_to_dict(old_ds)# new_ds_dic = model_to_dict(new_ds)# diff = old_ds_dic.keys() & new_ds_dic# diff_vals = [(k + ': from ' + str(old_ds_dic[k]) + ' to ' + str(new_ds_dic[k])) for k in diff if# old_ds_dic[k] != new_ds_dic[k]]# print(diff_vals)return api_success('信息保存成功!Data loading')return api_bad_request('数据表单验证失败,无法保存!')def delete(self, request, id):# print(request, id)ds = get_object_or_404(DataStructure, pk=id)res = delete_data(ds)if res:return api_success(res)return api_bad_request('数据删除失败!')
封装的文件处理函数public.py
,也是多对多外键attachments字段数据库处理的方式
def handle_files(request, files_obj, obj, FileSerializer):"""该函数传参对象有attachments属性时才可调用"""files = []for file_obj in files_obj:filename = file_obj.namesuffix = filename.rsplit(".", 1)[1]file_data = {'file': file_obj,'filename': filename,'suffix': suffix,}if request.method == 'PUT':# 1.本地文件删除obj_files = obj.attachments.all()for file in obj_files:file.file.delete()# 2.文件数据记录删除(先删除子表数据记录)obj_files.delete()fs = FileSerializer(data=file_data)if fs.is_valid():new_file = fs.save()new_file.content_object = objnew_file.save()files.append(fs.data.get('id'))if request.method == 'POST':obj.attachments.add(*files)if request.method == 'PUT':obj.attachments.set(files)def delete_data(obj):"""该函数传参对象有attachments属性时才可调用"""# 设置事务,保证数据的同步with transaction.atomic():save_point = transaction.savepoint()try:# 1.本地文件删除files = obj.attachments.all()if files:for file in files:file.file.delete()# 2.文件数据记录删除(先删除子表数据记录)files.delete()# 3.数据删除(再父表数据记录删除)obj.delete()except Exception as e:transaction.rollback(save_point)logger.info('obj->{} file delete error:{}'.format(obj, e))else:transaction.savepoint_commit(save_point)msg = '数据删除成功!Data loading......'return msgdef get_page_all_data(total, rows, sortOrder, pageNumber, pageSize):for i in range(len(rows)):if sortOrder == 'desc':no = total - (pageNumber - 1) * pageSize - ielse:no = (pageNumber - 1) * pageSize + i + 1rows[i]['no'] = nodata = {"total": total, "rows": rows}return data
3.分页查询数据处理
class DataStructureListView(APIView):def get(self, request):pageSize = int(request.GET.get('pageSize', 10))pageNumber = int(request.GET.get('pageNumber', 1))search_kw = request.GET.get('search_kw', '')sortName = request.GET.get('sortName', '')sortOrder = request.GET.get('sortOrder', '')ds_all = DataStructure.objects.all()# 查询if search_kw:ds_all = ds_all.filter(Q(title__icontains=search_kw) | Q(description__icontains=search_kw) | Q(full_name__icontains=search_kw))if sortName == 'no':sortName = 'id'# 排序if sortOrder == 'desc':ds_list = ds_all.order_by('-{}'.format(sortName))[(pageNumber - 1) * pageSize:(pageNumber) * pageSize]else:ds_list = ds_all.order_by(sortName)[(pageNumber - 1) * pageSize:(pageNumber) * pageSize]total = ds_all.count()# print(ds_list)# page_data = self.get_page_data(ds_list, total, sortOrder, pageNumber, pageSize)rows = TeamDataStructureQuerySerializer(ds_list, many=True).datapage_data = get_page_all_data(total, rows, sortOrder, pageNumber, pageSize)if page_data:return JsonResponse(page_data)return api_bad_request('数据加载失败!')def get_page_data(self, obj_list, total, sortOrder, pageNumber, pageSize):"""不使用序列化器,前端需要修改attachment字段获取方式"""ds_list_len = len(obj_list)rows = []data = {"total": total, "rows": rows}for i in range(ds_list_len):attachments = {}attachments['file_name'] = [i.filename for i in obj_list[i].attachments.all()]attachments['id'] = [i.id for i in obj_list[i].attachments.all()]if sortOrder == 'desc':no = total - (pageNumber - 1) * pageSize - ielse:no = (pageNumber - 1) * pageSize + i + 1row = {'id': obj_list[i].id,'no': no,'title': obj_list[i].title,'description': obj_list[i].description,'team': obj_list[i].team,'created_date': obj_list[i].created_date,'full_name': obj_list[i].full_name,'knowledge_category': obj_list[i].knowledge_category,'attachments': attachments,}rows.append(row)return data
下面附上前端分页的代码
{% extends "base.html" %} {% load static %}{% block main %}<style>.table th, .table td, .table b {text-align: center;vertical-align: middle !important;word-break: break-all;}</style><section class="section" id="da_kf"><div class="container"><div class="row justify-content-center"><div class="col-12 text-center"><div class="section-title mb-2"><h4 class="title mb-4">Key Features</h4><p class="text-muted para-desc mx-auto mb-0">Add some introduction here!</p></div></div><!--end col--></div><!--end row--><div class="row"><div class="col-lg-4 col-md-4 mt-4 pt-2"><div class="card features fea-primary rounded p-4 bg-light text-center position-relative overflow-hidden border-0"><span class="h1 icon2 text-primary"><a href="{% url 'stdfiles:download' 'HighRise Parameters & Values List' %}" class="text-reset">
{# <img src="{% static 'images/homepage/file-publisher.svg' %}" class="avatar avatar-md-sm"#}
{# alt="">#}<i class="fas fa-stream"></i></a></span><div class="card-body p-0 content"><h6><a href="{% url 'stdfiles:download' 'HighRise Parameters & Values List' %}"class="text-reset">Highrise ParameterList</a></h6></div><span class="big-icon text-center"><i class="fas fa-stream"></i></span></div></div><!--end col--><div class="col-lg-4 col-md-4 mt-4 pt-2"><div class="card features fea-primary rounded p-4 bg-light text-center position-relative overflow-hidden border-0"><span class="h1 icon2 text-primary"><a href="{% url 'registers:comingsoon' %}" class="text-reset"><i class="fas fa-braille"></i></a></span><div class="card-body p-0 content"><h6><a href="{% url 'registers:comingsoon' %}" class="text-reset">Common Platform ParameterList</a></h6></div><span class="big-icon text-center"><i class="fas fa-braille"></i></span></div></div><!--end col--><div class="col-lg-4 col-md-4 mt-4 pt-2"><div class="card features fea-primary rounded p-4 bg-light text-center position-relative overflow-hidden border-0"><span class="h1 icon2 text-primary"><a href="{% url 'registers:comingsoon' %}" class="text-reset"><i class="fas fa-cogs"></i></a></span><div class="card-body p-0 content"><h6><a href="{% url 'registers:comingsoon' %}" class="text-reset">Other Features</a></h6></div><span class="big-icon text-center"><i class="fas fa-cogs"></i></span></div></div><!--end col--></div></div><!--end container--></section><!--end section--><!-- Key feature End --><!-- data structure index start --><section class="section bg-light"><div class="container"><div class="row justify-content-center"><div class="col-12 text-center"><div class="section-title mb-4 pb-2"><h4 class="title mb-4">Data Structure List</h4></div></div><!--end col--></div><!--end row--><div id="toolbar"><div class="form-inline" role="form"><button id="remove" class="btn btn-soft-primary btn-sm"onclick="window.open('/teams/add_data_structure/')"><i class="fa fa-plus"></i> Add new data structure</button><!-- 自定义搜索查询 --><div class="input-group" style="left: 5px"><input id="search-keyword"class="form-control search-input" type="search"placeholder="Search" autocomplete="off"><div class="input-group-append"><button id="search-button" class="btn btn-soft-primary btn-sm" type="button" name="search"title="Search"><i class="fa fa-search"></i></button></div></div></div></div><!-- data structure index pagination --><table id="table"class="table-sm small"data-pagination="true"data-buttons-class="soft-primary btn-sm"data-sort-name="created_date"data-sort-order="desc"data-remember-order="true"data-show-fullscreen="true"data-show-columns="true"data-show-columns-toggle-all="true"data-show-export="true"data-click-to-select="true"data-toolbar="#toolbar"style="table-layout: fixed;height: auto"><thead class="thead-light"></table></div></section><!-- data structure index end --><!-- Team Members Start --><section class="section" id="da_team"><!-- Companies Start --><div class="container"><div class="row justify-content-center"><div class="col-12 text-center"><div class="section-title mb-2"><h4 class="title mb-4">Team Members</h4><p class="text-muted para-desc mx-auto mb-0">Add some introduction here!</p></div></div><!--end col--></div><!--end row--><div class="row"><div class="col-lg-4 col-md-6 col-12 mt-4 pt-2"><a href="page-job-detail.html"><div class="media key-feature align-items-center p-3 rounded shadow"><i class="fas fa-user-check"></i><div class="media-body ml-3"><h4 class="title mb-0 text-dark">Tian Xia</h4><p class="text-muted mb-0">Manager,....</p></div></div></a></div><!--end col--><div class="col-lg-4 col-md-6 col-12 mt-4 pt-2"><a href="page-job-detail.html"><div class="media key-feature align-items-center p-3 rounded shadow"><i class="fas fa-user-check"></i><div class="media-body ml-3"><h4 class="title mb-0 text-dark">Yang Fang</h4><p class="text-muted mb-0">Responsibility</p></div></div></a></div><!--end col--><div class="col-lg-4 col-md-6 col-12 mt-4 pt-2"><a href="page-job-detail.html"><div class="media key-feature align-items-center p-3 rounded shadow"><i class="fas fa-user-check"></i><div class="media-body ml-3"><h4 class="title mb-0 text-dark">Wang Xiaonan</h4><p class="text-muted mb-0">Responsibility</p></div></div></a></div><!--end col--><div class="col-lg-4 col-md-6 col-12 mt-4 pt-2"><a href="page-job-detail.html"><div class="media key-feature align-items-center p-3 rounded shadow"><i class="fas fa-user-check"></i><div class="media-body ml-3"><h4 class="title mb-0 text-dark">Ding Chenchen</h4><p class="text-muted mb-0">Responsibility</p></div></div></a></div><!--end col--></div><!--end row--></div><!--end container--><!-- Companies End --></section><!-- Team Members End -->
{% endblock %}{% block script %}<!-- JS for download table--><script>var $table = $('#table')$(function () {$('#toolbar').find('select').change(function () {$table.bootstrapTable('destroy').bootstrapTable({exportDataType: $(this).val(),exportTypes: ['excel', 'xml', 'csv', 'txt', 'sql'],columns: [{field: 'state',checkbox: true,visible: $(this).val() === 'selected'}]})}).trigger('change')})</script><!-- JS for pagination --><script>var $articlesTable = $('#table').bootstrapTable('destroy').bootstrapTable({url: '/teams/data_structure_list/',method: 'GET',dataType: "json",uniqueId: 'id', //每一行的唯一标识,一般为主键列striped: false, //是否显示行间隔色cache: false,sortName: 'no',sortable: true,sortOrder: 'desc',sidePagination: "server",undefinedText: '--',singleSelect: true,toolbar: '#toolbar', //工具按钮用哪个容器showToggle: true, //是否显示详细视图和列表视图的切换按钮strictSearch: true,clickToSelect: true,pagination: true, //是否显示分页(*)showRefresh: true, //是否显示刷新按钮pageNumber: 1, //初始化加载第一页,默认第一页pageSize: 10, //每页的记录行数(*)pageList: [10, 20, 50, 100, 'all'],paginationPreText: "<",paginationNextText: ">",queryParamsType: "",maxHeight: "200px",queryParams: function (params) {var query_params = {'pageSize': params.pageSize,'pageNumber': params.pageNumber,'search_kw': $('#search-keyword').val(), // 查询框中的参数传递给后台'sortName': params.sortName,'sortOrder': params.sortOrder};return query_params;},columns: [{field: 'no',title: 'No',align: 'center',halign: 'center',width: '60px',visible: true,sortable: true,formatter: function (value, row, index) {var result = "";result += '<a href="/teams/data_structure_detail/' + row.id + '/">' + row.no + '</a>'return result}},{field: 'id',title: 'DB_Id',align: 'center',halign: 'center',width: '70px',visible: false,sortable: true,formatter: function (value, row, index) {{#console.log(row)#}var result = "";result += '<a href="/teams/data_structure_detail/' + row.id + '/">' + row.id + '</a>'return result}},{field: 'title',title: 'title',align: 'left',halign: 'center',width: '180px',visible: true,formatter: function (value, row, index) {var result = "";result += '<a href="/teams/data_structure_detail/' + row.id + '/">' + row.title + '</a>'return result}},{field: 'description',title: 'Description',align: 'left',halign: 'center',width: '180px',visible: true,},{field: 'team',title: 'Team',align: 'left',halign: 'center',width: '100px',visible: true,},{field: 'knowledge_category',title: 'Knowledge Category',align: 'left',halign: 'center',width: '170px',visible: true,},{field: 'full_name',title: 'Owner',align: 'left',halign: 'center',width: '120px',visible: true,sortable: true,},{field: 'created_date',title: 'Upload date',align: 'center',halign: 'center',width: '120px',visible: true,sortable: true,},{field: 'attachments',title: 'Files',align: 'left',halign: 'center',width: '180px',visible: true,formatter: function (value, row, index) {var result = "";// 后台不使用序列化器{#for (var i = 0; i < row.attachments.id.length; i++) {result += '<a href="/file/teams_data_structure_file_download/' + row.attachments.id[i] + '">' + row.attachments.file_name[i] + ';<br/></a>'}#}// 后台使用序列化器for (var i = 0; i < row.attachments.length; i++) {result += '<a href="/file/teams_data_structure_file_download/' + row.attachments[i].id + '">' + row.attachments[i].file_name + ';<br/></a>'}return result}},{title: 'Operation',halign: 'center',width: '80px',visible: true,formatter: function (value, row, index) {var result = "";result += '<a href="/teams/data_structure_detail/' + row.id + '/" target="_blank"><i class="fas fa-edit"></i></a> 'result += '<a href="#" οnclick="del_data_structure(' + row.id + ')"><i class="fas fa-trash-alt"></i></a>'return result}}],onLoadError: function (data) {console.log("数据加载失败!", "错误提示");$.messager.alert({title: '提示', msg: '数据加载失败!', icon: 'warning', top: 200});},});// 搜索查询按钮触发事件$("#search-button").click(function () {console.log($('#search-keyword').val())$('#table').bootstrapTable(('refresh'));})// 回车执行搜索$("#search-keyword").bind('keyup', function (event) {console.log($('#search-keyword').val())$('#table').bootstrapTable(('refresh'));})</script><!-- JS for delete data --><script>function del_data_structure(id) {console.log(id)$.messager.confirm({title: '提示', msg: '是否确认删除该条数据?', top: 200, fn: function (r) {if (r) {$.ajax({url: server_url + '/teams/data_structure_detail/' + id + '/',method: 'delete',processData: false,contentType: false,cache: false,success: function (data) {console.log("data:" + data);console.log("data:" + data.status);if (data.status === 200) {{#$.messager.alert({title: '提示', msg: data.msg, icon: 'warning', top: 200,});#}$.messager.show({title: '提示',msg: data.msg,showType: '',timeout: 500,style: {top: 200}});console.log("data:" + data.msg);$('#table').bootstrapTable('refresh');{#window.setTimeout("window.location=server_url+'/teams/data_structure/'", 600);#}return}console.log(data)$.messager.alert({title: '提示', msg: '权限不足或服务请求异常,数据无法删除!', icon: 'warning', top: 200});},//请求失败,包含具体的错误信息error: function (data) {console.log('error' + data.msg);$.messager.alert({title: '提示', msg: '请求服务错误或当前网络不佳!', icon: 'warning', top: 200});}});}}});}</script>
{% endblock %}
免声明:以上内容和代码仅供参考!