基于DRF的图书增删改查
功能演示
信息展示
添加功能
编辑功能
删除功能
DRF构建后台数据
本例的Model如下
from django.db import models class Publish(models.Model): name = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32,verbose_name='姓名') class Book(models.Model): title = models.CharField(verbose_name='书名',max_length=56) price = models.DecimalField(verbose_name='价格',max_digits=8,decimal_places=2) pub_date = models.DateField(verbose_name='出版日期') publish = models.ForeignKey(to=Publish,on_delete=models.CASCADE) authors = models.ManyToManyField(to=Author)
注册DRF
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'book.apps.BookConfig', 'rest_framework', ]
路由分发如下
# 查看与新增—— GET与POST url(r'^books/$',views.BookListView.as_view(),name='book_get_post'), # 修改与删除—— PUT与DELETE url(r'^book/(?P<pk>\d+)/$',views.BookView.as_view(),name='book_put_delete'),
视图函数如下
from rest_framework.views import APIView from rest_framework.response import Response from book import models from book.my_serializer import BookSerializer class BookListView(APIView): def get(self, request, *args, **kwargs): """ 获取书籍信息 """ # 用自定义的序列化器去实现~~~ all_books = models.Book.objects.all() # 第一个参数是instance~是一个对象 # 但是all()方法查出来的是一个“对象列表”——所以需要加many=True ser_obj = BookSerializer(all_books, many=True) # 返回自定义序列化器的data return Response(ser_obj.data) def post(self, request, *args, **kwargs): """新增数据 返回新建的书籍的数据 json格式 """ # 用序列化器进行校验!!! # 注意:这里用的是request.data去取新增的值!!! print('>>>>>>',request.data) ser_book = BookSerializer(data=request.data) if ser_book.is_valid(): ser_book.save() # 校验成功并且成功保存的话~返回新增的数据! return render(request,'book_list.html') else: print(ser_book.errors) return Response(ser_book.errors) class BookView(APIView): def get(self,request,pk,*args,**kwargs): # 找Model对象 book_obj = models.Book.objects.filter(pk=pk).first() # 序列化器对象——此时instance只有一个book_obj,不用加many=True了! ser_obj = BookSerializer(instance=book_obj) # 用Response方法返回序列化器对象的data return Response(ser_obj.data) def put(self,request,pk,*args,**kwargs): book_obj = models.Book.objects.filter(pk=pk).first() # partial=True —— 表示支持“部分提交/局部更新” ser_obj = BookSerializer(instance=book_obj,data=request.data,partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.data) else: return Response(ser_obj.errors) # 删除方法不需要用序列化器了 def delete(self,request,pk,*args,**kwargs): obj = models.Book.objects.filter(pk=pk).first() if obj: obj.delete() return Response({'msg':'删除成功!'}) else: return Response({"error":'数据不存在!'})
自定义的序列化器代码如下
# -*- coding:utf-8 -*- from rest_framework import serializers from book import models class PublishSerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) name = serializers.CharField() class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField() class BookSerializer(serializers.Serializer): # 与Book中的属性对应上 # id 也需要~后面编辑与删除用得到~~设置read_only,添加的时候不必填 id = serializers.IntegerField(read_only=True) title = serializers.CharField() price = serializers.DecimalField(max_digits=8,decimal_places=2) pub_date = serializers.DateField() # 外键的~这个字段其实存的是id~~注意这里是publish_id——数据库中存储的字段~~但是这种方式只能拿到id值 # publish_id = serializers.IntegerField() # 多对一 外键关联~ # 如果我们想拿publish的name的话,就需要交给上一个序列化器PublishSerializer去处理 # 提交的时候~~不用填这个,所以设置required=False # 只有get请求要他而post请求不用它~所以设置 read_only=True publish = PublishSerializer(required=False,read_only=True) # 多对多~ # 只有get请求要他而post请求不用它:read_only=True # 下面必须有一个 get_字段名 的方法对应! authors = serializers.SerializerMethodField(read_only=True) # post提交用这个字段~是int类型的 # get请求不要他~~设置 write_only=True post_publish = serializers.IntegerField(write_only=True) # post提交用这个字段~是一个ListField~列表里是数字 # get请求不要他~~设置 write_only=True post_authors = serializers.ListField(write_only=True) # 多对多关系查找authors用到的方法——与上面的SerializerMethodField对应 def get_authors(self,obj): # 注意~obj是Book对象!! # print(obj) # 基于对象的跨表查询~注意是多个对象了~many应该设置为True ser_obj = AuthorSerializer(obj.authors.all(),many=True) return ser_obj.data # POST方式增加数据需要 def create(self, validated_data): # post提交的时候~~重写create方法 # post提交给的数据应该是这种格式的 # 注意后面那两个是post_publish、post_authors~专门用于提交的字段 """ { "title": "西游记", "price": 12.20, "pub_date": "2019-12-22T10:10:11Z", "post_publish": 1, "post_authors": [1,2] } """ print('validated_data>>>',validated_data) book_obj = models.Book.objects.create( title=validated_data.get('title'), price=validated_data.get('price'), pub_date=validated_data.get('pub_date'), publish_id=validated_data.get('post_publish'), ) # 多对多插入数据~~基于对象的跨表查询 # 注意用set方法存多对多关系的数据 book_obj.authors.set(validated_data.get('post_authors')) return book_obj # PUT请求修改数据需要写的方法 def update(self, instance, validated_data): # 如果取到了就用修改的~~如果没有就用原来的数据 instance.title = validated_data.get('title',instance.title) instance.prince = validated_data.get('price',instance.price) instance.pub_date = validated_data.get('pub_date',instance.pub_date) # 上面设置了post_publish为write_only了~所以修改要用post_publish instance.publish_id = validated_data.get('post_publish',instance.publish_id) # 先save~然后再处理一下多对多关系的数据 instance.save() # 基于对象的跨表查询~~注意用set方法存多对多关系的数据 # 如果没有的话需要用all方法取出所有对象~~ # # 上面设置了post_authors为write_only了~所以修改要用post_authors instance.authors.set(validated_data.get('post_authors',instance.authors.all())) # 最后记得把instance 返回 return instance
在DRF自带的页面进行数据的增删改查测试
至此DRF就写好了,我们可以根据路由去访问对应的页面进行数据的增删改查操作(需要注意,必须先在settings中注册了rest_framework后才能访问DRF自带的页面)
DRF自带的页面是这样的:
当然,我们不能让用户看这样的页面,这就需要前端请求DRF构建好的数据进行标签的构建了。
前端请求DRF构建好的数据并构建页面效果
测试路由如下
# 书籍展示的页面 url(r'^book_list/$',views.book_list,name='book_list'), # 添加书籍的页面 url(r'^add_book_view/$',views.add_book,name='add_book_view'),# 编辑书籍的展示页面~~ url(r'edit_book_view/(?P<pk>\d+)/$',views.edit_book,name='edit_book'),
视图函数如下
视图函数非常简单,再加上是进行数据测试,所以这里的视图函数只负责返回页面。
数据的操作全部是用ajax与js做的。
# 展示 书籍列表 def book_list(request): return render(request,'book_list.html') # 展示 添加书籍页面 def add_book(request): return render(request,'add_book.html') # 编辑书籍的展示页面 def edit_book(request,pk): return render(request,'edit_book.html')
所有页面的母版
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock title %}</title> <link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.css' %}"> <style> {# th中的文字剧中 bootstrap设置的是left #} th { text-align: center; } </style> </head> <body style="padding-top:52px;"> <!--导航 独立于页面,不包含在盒子里面。不要放在container里面 --> <nav class="navbar navbar-default navbar-fixed-top navbar-inverse"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">火之国</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse pull-right" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li><a href="#">Link</a></li> <li><a href="#">Link</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="pannel panel-danger"> <!--panel-heading--> <div class="panel-heading"> <!--panel-title--> <h3 class="panel-title">火之国图书管理系统</h3> </div> <!--panel-body--> <div class="panel-body"> <!--把其他的组件放到panel-body里面--> <!--block --> {% block pannel-body %} {% endblock pannel-body %} </div> </div> </div> </div> </div> <script src="{% static 'jquery-3.4.1.js' %}"></script> <script src="{% static 'bootstrap-3.3.7/js/bootstrap.js' %}"></script> {% block script %} {% endblock script %} </body> </html>
书籍展示页面及删除书籍的功能
书籍展示发送的是get请求。
删除书籍发送的是delete请求。
{% extends 'base.html' %} {% block title %} 主页 {% endblock title %} {% block pannel-body %} {% csrf_token %} <a id="add_book" href="{% url 'add_book_view' %}" class="btn btn-success pull-right">添加书籍</a> <br><br> <div id="div_table" class="table-responsive" style="text-align: center"> <table id="table" class="table table-striped table-bordered table-hover table-condensed"> <thead> <tr class="success"> <th>编号</th> <th>书籍名称</th> <th>价格</th> <th>出版日期</th> <th>出版社</th> <th>作者</th> <th>操作</th> </tr> </thead> {# 委托的父级标签用tbody #} <tbody id="tbody"> </tbody> </table> </div> {% endblock pannel-body %} {% block script %} <script> // 格式化时间的函数 function formatDate(time) { var date = new Date(time); var year = date.getFullYear(), month = date.getMonth() + 1,//月份是从0开始的 day = date.getDate(), hour = date.getHours(), min = date.getMinutes(), sec = date.getSeconds(); var newTime = year + '-' + month + '-' + day + ' ' + hour + ':' + min + ':' + sec; return newTime; } // 页面加载自动触发ajax请求~向DRF获取所有数据并在前端渲染 $(document).ready(function () { $.ajax({ url: '/books/', type: 'get', success: function (data) { console.log(data, typeof (data)); // data是一个object for (var i = 0; i < data.length; i++) { // data[i]是一个个自定义对象 //console.log(data[i],typeof(data[i])); var tr = document.createElement('tr'); var td_num = document.createElement('td'); // 提前把编号写进tr中去 注意同时将id也加进去 td_num.innerHTML = (i + 1) + '<span class="book_pk" style="display: none">' + data[i].id + '</span> </td>'; //这时tr的第一个元素就是一个个的编号——并且里面的span标签带着每个数据的id tr.append(td_num); for (var j in data[i]) { //console.log(j); // 不用填id字段 if (j === 'id') { continue; } // 新建一个td标签,把遍历的数据加进去 var td = document.createElement('td'); //格式化一下出版日期的格式 if (j === 'pub_date') { data[i][j] = formatDate(data[i][j]); } //展示出版社的名字 if (j === 'publish') { data[i][j] = data[i][j]['name']; } //展示作者的名字 if (j === 'authors') { //console.log(data[i][j]); var authors = ''; for (var k in data[i][j]) { authors += data[i][j][k]['name'] + ' '; } data[i][j] = authors; } td.append(data[i][j]); tr.append(td); } //循环完,最后把编辑与删除按钮添加进去 var tdd = document.createElement('td'); tdd.innerHTML = '<td><a class="btn btn-primary edit_book"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span><span>编辑</span>\n' + '</a><a class="btn btn-danger del_book"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span><span>删除</span></a></td>'; tr.append(tdd); // 最后将tr加到tbody中 $('#tbody').append(tr); } } }) }); // 删除按钮的点击事件 // 用委托实现 // 这里也可以加一个"模态对话框"~给用户一个确认删除删除的提示 $('#tbody').on('click', '.del_book', function () { // 找到这本书对应的id~ // console.log($(this).parent().parent().find('.book_pk').text()); var book_id = $(this).parent().parent().find('.book_pk').text(); $.ajax({ url: '/book/' + book_id + '/', type: 'delete', success: function (data) { location.href = '/book_list/'; } }) }); // 编辑按钮的点击事件 // 用委托实现 $('#tbody').on('click', '.edit_book', function () { // 找到这本书对应的id~ var book_id = $(this).parent().parent().find('.book_pk').text(); $.ajax({ url:'/book/'+book_id+'/', type:'put', success:function (data) { // data是待编辑书籍的数据 console.log(data,typeof(data)); // 序列化数据 data_json = JSON.stringify(data); // 将数据存到session中 sessionStorage.edit_book_data = data_json; // 跳转到编辑书籍页面 location.href = '/edit_book_view/' + book_id +'/'; } }) }) </script> {% endblock script %}
添加书籍页面
添加书籍发送的是post请求。
{% extends 'base.html' %} {% block title %} 主页 {% endblock title %} {% block pannel-body %} <div class="col-md-8 col-md-offset-2"> <h2 class="text-center">添加书籍</h2> <div> <div class="form-group"> <label for="book_name">书籍名称</label> <input type="text" id="book_name" class="form-control" placeholder="书籍名称" autocomplete="off"> <span class="help-block"></span> </div> <div class="form-group"> <label for="price">价格</label> <input type="number" id="price" class="form-control" placeholder="价格" autocomplete="off"> <span class="help-block"></span> </div> <div class="form-group"> <label for="pub_date">出版日期</label> <input type="date" id="pub_date" class="form-control" placeholder="出版日期"> <span class="help-block"></span> </div> <div class="form-group"> <label for="">出版社</label> <select id="publish" class="form-control"> <option value="1">苹果出版社</option> <option value="2">西瓜出版社</option> </select> </div> <div class="form-group"> <label for="">作者</label> <select name="authors" id="authors" class="form-control" multiple> <option value="1">whw</option> <option value="2">naruto</option> <option value="3">sasuke</option> </select> </div> <div class="form-group"> <h4 id="add_error" class="pull-left" style="color:red;margin-top: 0"></h4> <input id="confirm_add" type="button" class="btn btn-success pull-right" value="确认添加"> </div> </div> </div> {% endblock pannel-body %} {% block script %} <script> {# 确认按钮 #} $('#confirm_add').click(function () { {#console.log(123123);#} var title = $('#book_name').val(); var price = $('#price').val(); var pub_date = $('#pub_date').val(); // 下拉列表被选中的这样选取 var publish = $('#publish option:selected').val(); //ajax操作 $.ajax({ url: '{% url "book_get_post" %}', type: 'post', data: { title: title, price: price, pub_date: pub_date, //pub_date: "2019-08-02T09:35:13.064532Z", post_publish: publish, //post_authors: authors, post_authors: $('#authors').val(), }, // 传数组 traditional: true, success: function (data) { console.log(data); //alert('添加成功!'); location.href = '{% url "book_list" %}'; } }) }); </script> {% endblock script %}
编辑书籍页面
编辑书籍这里需要说一下过程:
(1)首先我在书籍展示那里点击“编辑”的时候,先把当前点击的书籍的信息取出来,然后序列化,最后将序列化的数据存在session中。
(2)然后在编辑页面从session中获取当前需要编辑的书籍的信息并把这些信息显示在前端的input框中。
(3)最后根据用户输入的数据保存书籍信息。
{% extends 'base.html' %} {% block title %} 主页 {% endblock title %} {% block pannel-body %} <div class="col-md-8 col-md-offset-2"> <h2 class="text-center">编辑书籍</h2> <div> <div class="form-group"> <label for="book_name">书籍名称</label> <input type="text" id="book_name" class="form-control" placeholder="书籍名称" autocomplete="off"> <span class="help-block"></span> </div> <div class="form-group"> <label for="price">价格</label> <input type="number" id="price" class="form-control" placeholder="价格" autocomplete="off"> <span class="help-block"></span> </div> <div class="form-group"> <label for="pub_date">出版日期</label> <input type="date" id="pub_date" class="form-control" placeholder="出版日期"> <span class="help-block"></span> </div> <div class="form-group"> <label for="">出版社</label> <select id="publish" class="form-control"> <option value="1">苹果出版社</option> <option value="2">西瓜出版社</option> </select> </div> <div class="form-group"> <label for="">作者</label> <select name="authors" id="authors" class="form-control" multiple> <option value="1">whw</option> <option value="2">naruto</option> <option value="3">sasuke</option> </select> </div> <div class="form-group"> <h4 id="add_error" class="pull-left" style="color:red;margin-top: 0"></h4> <input id="confirm_add" type="button" class="btn btn-success pull-right" value="确认编辑"> </div> </div> </div> {% endblock pannel-body %} {% block script %} <script> // 页面加载后将session中的数据写到上面的标签中 $(document).ready(function () { // 获取session中的数据 var data_session = sessionStorage['edit_book_data']; // 记得反序列化一下 var data = JSON.parse(data_session); console.log(data, typeof (data)); // 取出edit_book_id 把它设置为全局的变量!后面ajax提交的时候会用到 edit_book_id = data['id']; // 将数据填在上面的input框中~注意是val方法! $('#book_name').val(data['title']); $('#price').val(data['price']); $('#pub_date').val(data['pub_date']); $('#publish').val(data['publish']['id']); // 让之前的作者名被选中 var arr_val = []; for(var i in data['authors']){ //console.log(data['authors'][i]['id']); arr_val.push(data['authors'][i]['id']) } // console.log(arr_val); [1,2] // 把数组传给复选框的val~让之前的作者被选中 $('#authors').val(arr_val); }); // 确认编辑按钮 $('#confirm_add').click(function () { var title = $('#book_name').val(); var price = $('#price').val(); var pub_date = $('#pub_date').val(); // 下拉列表被选中的这样选取 var publish = $('#publish option:selected').val(); //ajax操作 $.ajax({ url: '/book/'+edit_book_id+'/', type: 'put', data: { title: title, price: price, pub_date: pub_date, //pub_date: "2019-08-02T09:35:13.064532Z", post_publish: publish, //post_authors: authors, post_authors: $('#authors').val(), }, // 传数组~ traditional: true, success: function (data) { console.log(data); //alert('添加成功!'); location.href = '{% url "book_list" %}'; } }) }); </script> {% endblock script %}
衣带渐宽终不悔,为伊消得人憔悴!