django rest

    科技2024-05-16  87

    本人是python业余选手,公司的系统是我业余时间开发的,一直在维护升级。 前段时间更新了一个业务人员下单的功能

    上方的是本地开发的页面和数据,实际业务中,订单会很多,每天会增加几十条订单记录,那订单的筛选必不可少,而且是多条件查询,这是刚需。

    先对需求做一个整理:

    多条件交叉查询异步请求

    程序采用的是django 2.1.8,跑在本地的windows sever2008上,使用apache2.4部署,前端使用的是django前端模板语言,那么以下几点是可以明确的

    django rest_framework前端使用原生js写请求和数据渲染(jq还未学习,不熟悉)

    js原生异步请求思路

    数据渲染思路

    异步请求有响应后,不管有无数据,都需要清除原来页面的数据以及分页

    if(有数据){ 清除原有数据 清除原有分页 创建元素 //和原来一模一样的 渲染数据 //使用responseText()方法把后端响应的json数据转换成js对象 }else(无数据){ 清除原有数据 清除原有分页 提示:暂无数据 }

    前端流程的处理思路

    前端:

    抽象ajax模块,处理请求和响应(按理说,响应部分也需要抽象出来,这里偷懒了,直接写在了ajax模板中)抽象数据过滤模块,使用addEventListner()监听多条件点击事件,拼接url,发起异步请求抽象响应后的分页addEventListner()点击监听模块,如果存在page参数,则拼接到url中,发起异步请求分页监听模块,过滤数据后进行调用(如果数据存在)

    核心代码展示

    获取元素,声明变量 // 获取元素,页面上的4个过滤条件 var orderStatus = document.querySelector("#order-status-items").children var expenseStatus = document.querySelector("#expense-status-items").children var sales = document.querySelector("#sales-items").children var designer = document.querySelector("#designer-items").children // 获取当前的url,后面做url拼接 var host = document.location // 请求后的分页,分页是在ajax请求后再生成的,所以要在数据过滤模块外进行保存该变量 var pages // 定义4个过滤条件的url默认值,xx-xx-all代表“全部xx”,如果前端选择的是其他过滤条件,则声明为实际的过滤条件,这个值由前端元素中声明 var orderStatusCode = "order-status-all"; var expenseStatusCode = "expense-status-all"; var salesCode = "sales-status-all"; var designerCode = "designer-status-all"; //异步请求和响应处理模块 var JsonRequest = function(orderStatusCode, expenseStatusCode, salesCode, designerCode, page) { var osc = orderStatusCode var esc = expenseStatusCode var sc = salesCode var dc = designerCode // 1.创建XMLHttprequest对象 var xhr = new XMLHttpRequest(); // 2.调用open方法打开URL // 如果分页参数page存在,即在url后面拼接&page=x,不存在便只拼接过滤的url if (page) { xhr.open("get", "filter/?osc=" + osc + "&esc=" + esc + "&sc=" + sc + "&dc=" + dc + "&page=" + page); } else { xhr.open("get", "filter/?osc=" + osc + "&esc=" + esc + "&sc=" + sc + "&dc=" + dc); } // 3.发送异步请求 xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.send() // 4.侦听请求状态码 xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { // 获取返回的结果,使用json解析 var res = JSON.parse(xhr.responseText) var page_obj = res["data"] //清除当前页面的数据 var tbody = document.getElementById("order-body"); if (tbody) { tbody.parentNode.removeChild(tbody); } // 判断能否筛选到数据 res["flag"] == true if(res["flag"] == true){ // 有数据则: // 1.清除当前页面的数据和分页 // 2.写入请求到的数据和分页 // 3.启动分页点击侦听,请求分页数据 //写入过滤后的数据 // 创建table的子元素tbody,并设置id属性为order-body var tbody = document.createElement("tbody"); tbody.setAttribute("id", "order-body"); var table = document.querySelector("table"); table.appendChild(tbody); // 清除“哥,暂无结果”的提示,如果存在的话 var h4 = document.querySelector("#nomsg"); if(h4){ h4.parentNode.removeChild(h4); } // 创建tbody的子元素tr、子孙元素th和td,创建的规范符合原有的table规范 // 创建tr,数据有几行就创建几个tr,使用循环处理 for (i = 0; i < page_obj.length; i++) { console.log("遍历获取的数据:", page_obj[i]); var tr = document.createElement("tr"); // 创建th,即序号 var th = document.createElement("th"); th.setAttribute("scope", "row"); th.innerHTML = i + 1; tr.appendChild(th); // 创建td var tdTitle = document.createElement("td"); tdTitle.innerHTML = "<a href=" + host.origin + "/orders/detail/" + page_obj[i]["id"] + ">" + page_obj[i]["title"] + "</a>"; tr.appendChild(tdTitle); var tdCompany = document.createElement("td"); tdCompany.innerHTML = page_obj[i]["company"]; tr.appendChild(tdCompany); var tdCreateTime = document.createElement("td"); var dateTime = formatDate(page_obj[i]["created_time"]); tdCreateTime.innerHTML = dateTime; tr.appendChild(tdCreateTime); var tdEndTime = document.createElement("td"); var endTime = formatDate(page_obj[i]["end_time"]); tdEndTime.innerHTML = endTime; tr.appendChild(tdEndTime); var tdOrderStatus = document.createElement("td"); tdOrderStatus.innerHTML = page_obj[i]["order_status"]; tr.appendChild(tdOrderStatus); var tdExpenseStatus = document.createElement("td"); tdExpenseStatus.innerHTML = page_obj[i]["expense_status"]; tr.appendChild(tdExpenseStatus); var tdDesigner = document.createElement("td"); tdDesigner.innerHTML = page_obj[i]["designer"]; tr.appendChild(tdDesigner); var tdSales = document.createElement("td"); tdSales.innerHTML = page_obj[i]["sales"]; tr.appendChild(tdSales); // 把tr添加到tbody中 tbody.appendChild(tr) } // 清除分页页面:原有的分页及请求后产生的分页,请求后产生的分页,它们使用一致的id,以便简化代码 var defaultPaginator = document.querySelector("#dj-pagination"); // console.log(defaultPaginator); if (defaultPaginator) { defaultPaginator.parentNode.removeChild(defaultPaginator); } // 写入分页页面 var Ulpaginator = document.createElement("ul"); // 写入id,实现每次点击请求时都会清除该页面,然后再重写 Ulpaginator.setAttribute("id", "dj-pagination"); // 写入一个自定义属性,以此来区别该ul是异步请求后生成的 Ulpaginator.setAttribute("mark", "true"); // 写入样式,调用bootstrap默认样式 Ulpaginator.classList.add("pagination", "mt-3") var container = document.querySelector("#use-for-ajax"); container.appendChild(Ulpaginator); // 写入li,后端传过来的res["page_nums"]是分页后的页码总数,可以此来创建分页 var pageNum = res["page_nums"] for (var i = 0; i < pageNum; i++) { var pageLi = document.createElement("li"); pageLi.innerHTML = i + 1; pageLi.classList.add("page-item", "page-link"); pageLi.style.cursor = "pointer"; // console.log(pageLi); Ulpaginator.appendChild(pageLi); } // 异步请求清除原有分页数据后,才能获取新的分页,并保存在pages中(上面有声明该变量) pages = document.querySelector("#dj-pagination").children; // 启动分页点击侦听器 pageListner(pages); }else{ // 无数据,则: // 输出无数据的提示 // 依然要清除原有的分页和原有的页面数据 // 清除原有的分页页面 var defaultPaginator = document.querySelector("#dj-pagination"); // console.log(defaultPaginator); if (defaultPaginator) { defaultPaginator.parentNode.removeChild(defaultPaginator); } // 提示 var h4 = document.querySelector("#nomsg"); if(h4){ return; }else{ var h4 = document.createElement("h4"); h4.innerText = "哥,暂无数据"; h4.setAttribute("id", "nomsg"); var container = document.querySelector("#use-for-ajax"); container.appendChild(h4); } } } } } //条件过滤点击侦听函数 var vanListner = function(nav) { for (i = 0; i < nav.length; i++) { nav[i].addEventListener("click", function() { for (i = 0; i < nav.length; i++) { nav[i].className = ""; } this.className = "order_status_active"; // 获取父元素的id,以此区分当前元素的id值归属于哪部分 // 订单code if (this.parentNode.getAttribute("id") == "order-status-items") { tarGetId = this.getAttribute("id"); orderStatusCode = tarGetId; // 发送异步请求 JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode) } // 费用code if (this.parentNode.getAttribute("id") == "expense-status-items") { tarGetId = this.getAttribute("id"); expenseStatusCode = tarGetId; // 发送异步请求 JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode) } // 下单人code if (this.parentNode.getAttribute("id") == "sales-items") { tarGetId = this.getAttribute("id"); salesCode = tarGetId; // 发送异步请求 JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode) } // 设计code if (this.parentNode.getAttribute("id") == "designer-items") { tarGetId = this.getAttribute("id"); designerCode = tarGetId; // 发送异步请求 JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode) } }) } } // 分页点击侦听模块,该模块需要在异步请求模块中的过滤有数据后立即调用 var pageListner = function(pages) { // 分页侦听 // 点击后,获取异步请求后的分页数据 for (var i = 0; i < pages.length; i++) { pages[i].addEventListener("click", function() { // 去掉激活样式 for (var i = 0; i < pages.length; i++) { pages[i].classList.remove("active"); } this.classList.add("active"); // 获取当前请求参数,发送异步请求 JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode, this.innerText); }) } }

    以上代码写在一个叫js/order_filter.js的文件中,在前端页面中引入,并调用过滤条件侦听器

    <script src="{% static 'js/order_filter.js' %}" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> // 调用函数,导航点击激活、拼接url和发送异步请求 vanListner(orderStatus); vanListner(expenseStatus); vanListner(sales); vanListner(designer); </script>

    django后端处理逻辑

    django中有个叫order的app,用来处理订单。

    在order下创建一个序列化器文件: serializer.py 这个文件的命名不能使用serializers,不然会和serializers 模块冲突 在serializer.py中创建序列化器 from rest_framework import serializers from . models import Order class OrderSerializer(serializers.ModelSerializer): order_status = serializers.CharField(source='get_order_status_display') company = serializers.CharField(source='company.company_name') expense_status = serializers.CharField(source='get_expense_status_display') sales = serializers.CharField(source='sales.username') designer = serializers.CharField(source='designer.username') class Meta: model = Order fields = ['id', 'title', 'order_status', 'expense_status', 'sales', 'designer', 'company','items', 'created_time', 'end_time']

    对于Forenkey 和 choices 的字段,需要在序列化器中做友好可视化,不然前端只能看到id或choices的选项。

    在views.py中处理响应

    from order.models import Order from rest_framework import serializers from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.pagination import PageNumberPagination from order import serializer # 重写PageNumberPagination方法,获取页码总数 class DRFPagination(PageNumberPagination): def paginate_queryset(self, queryset, request, view=None): """ Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view. """ page_size = self.get_page_size(request) if not page_size: return None paginator = self.django_paginator_class(queryset, page_size) page_number = request.query_params.get(self.page_query_param, 1) if page_number in self.last_page_strings: page_number = paginator.num_pages try: self.page = paginator.page(page_number) except InvalidPage as exc: msg = self.invalid_page_message.format( page_number=page_number, message=str(exc) ) raise NotFound(msg) if paginator.num_pages > 1 and self.template is not None: # The browsable API should display pagination controls. self.display_page_controls = True self.request = request return list(self.page), paginator.num_pages class OrderSerializerView(APIView): def post(self, request, *args, **kwargs): print(request.data) obj = code_handler(osc=request.data['osc'], esc=request.data['esc'], sc=request.data['sc'], dc=request.data['dc']) #创建分页对象 page_numb_paginator = PageNumberPagination() page_obj = page_numb_paginator.paginate_queryset(queryset=obj, request=request, view=self) json_obj = serializer.OrderSerializer(instance=page_obj, many=True) return Response(json_obj.data) def get(self, request, *args, **kwargs): osc = request.GET.get('osc') esc = request.GET.get('esc') sc = request.GET.get('sc') dc = request.GET.get('dc') obj = code_handler(osc=osc, esc=esc, sc=sc, dc=dc) # page_numb_paginator = PageNumberPagination() 原生调用,后来继承重写,增加了page_num的返回 page_numb_paginator = DRFPagination() page_obj, num = page_numb_paginator.paginate_queryset(queryset=obj, request=request, view=self) json_obj = serializer.OrderSerializer(instance=page_obj, many=True) msg = '' flag = True drf_data = {} drf_data['page_nums'] = num drf_data['data'] = json_obj.data print(page_obj) print(len(page_obj)) if len(page_obj) < 1: msg = '暂无结果,请重新选择' flag = False if len(page_obj) >= 1: msg = '获取成功' drf_data['msg'] = msg drf_data['flag'] = flag return Response(drf_data)

    核心代码就这么多,第一次写原生js请求和渲染json数据,过程比较吃力,各种学习才能完整理解其中的原理

    Processed: 0.021, SQL: 8