Django DRF API(GET/POST/PUT)序列化和反序列化(一对多,多对多)demo-模型类序列化器ModelSerializer
admin
2024-03-21 21:12:40

一. 前言

在开发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 %}

Data Structure List

{% endblock %}{% block script %} {% endblock %}

免声明:以上内容和代码仅供参考!

相关内容

热门资讯

17道 特色旺销菜 恰恰茄子 原料: 糯长茄200克,香菜3克。 调料: 秘制茄子酱40克。 制作: 1.将长茄去皮后...
西藏攻略:7天6晚经典路线,带... 每年5月至10月,是西藏的季节,也是游客最多的时段。最近我们收到很多朋友的咨询:“次来西藏,只有7天...