Django中Q搜索的简单应用
本节涉及:
1.Q搜索在前后端的设计
2.Django中Queryset对象的序列化(由后端扔给前端的数据必然会经过序列化)
3.前端动态地构造表格以便显示(动态创建DOM对象)
思路:
用户通过前端查询数据库内容时,可添加多个搜索框,一个搜索框内可以输入多个条件。同一搜索框内的条件是或OR关系,不同搜索框间是与AND关系。如搜索图书,每条图书信息包括名称、页数、印刷日期、类型,在一个搜索框内可选择搜索书名,以中文逗号分隔即可以书名同时搜索多本图书,同一搜索框内就是OR关系。又可以再添加输入框,这时就可以再添加类型、价格等信息,缩小搜索范围,这些搜索框之间就是AND关系。条件传递给后端后,后端拿到结果,处理后再抛给前端,由前端在页面展示。
代码
数据库信息
class BookType(models.Model): caption = models.CharField(max_length=32) class Book(models.Model): name = models.CharField(max_length=32) pages = models.IntegerField() price = models.DecimalField(max_digits=10, decimal_places=2) pubdate = models.DateField() # 外键 book_type = models.ForeignKey(BookType, on_delete=models.CASCADE) def __str__(self): return "Book Object: %s %sp %s元" % (self.name, self.pages, self.price)
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="/static/js/jquery-2.1.4.min.js"></script> <title>Index页面</title> </head> <body> <!--搜索框--> <div class="condition"> <div class="item "> <!--点击后会添加一个搜索框--> <div class='icon' onclick="AddCondition(this);">+</div> <div> <!--选择不同的搜索条件时,会触发方法--> <select onchange="ChangeName(this);"> <option value="name">书名</option> <option value="book_type__caption">类型</option> <option value="price">价格</option> </select> </div> <div class="left"><input type="text" name="name"/></div> </div> </div> <div> <!--点击触发搜索,向后端传递--> <input type="button" onclick="Search();" value="搜索"> </div> <!--展示区--> <div class="container"> </div> </body>
JS代码
<script> function AddCondition(ths){ // dom对象转换为jquery对象 var new_tag = $(ths).parent().clone(); // 新添加的搜索框的按钮改为减号,定义删除方法 new_tag.find('.icon').text('-'); new_tag.find('.icon').attr('onclick','RemoveCondition(this);'); $(ths).parent().append(new_tag); } // 新添加的搜索框还可删除 function RemoveCondition(ths){ $(ths).parent().remove(); } // function ChangeName(tag){ // 获取搜索条件value属性的值 var v = $(tag).val(); // 定义input的name属性 // 这里主要是为了随着用户选择不同的搜索条件 // 也向后端传递不同的搜索条件,Q搜索中会用到 $(tag).parent().next().find('input').attr('name', v); } function Search(){ // 获取所有用户输入的内容并提交 // 将要给后端传递的字典 var post_data_dict = {}; // 循环所有的input $('.condition input').each( function(){ // 操作jquery对象 // 获得搜索条件 var attr_name = $(this).attr('name'); // 获取用户输入 var value_list = $(this).val().split(','); post_data_dict[attr_name] = value_list; } ); // 将要传递的字典序列化 var post_data_str = JSON.stringify(post_data_dict); $.ajax({ url: '/index/', type: 'POST', data: {'post_data': post_data_str}, // 此参数使得后端传来的json会被解析为js对象 dataType: "json", success: function(ret){ if(ret.status){ // 清空展示柜,避免重复显示 $('.container').empty(); // {'status':true, 'content': [{},{}]} $.each(ret.content, function(useless_key, value_dict){ // 有多少条数据就有多少个表 // 形式为一表一行n列 var table = document.createElement('table'); // 每个表有一行数据 var tr = document.createElement('tr'); // {'name':'xx', 'pages':540} $.each(value_dict, function(key, val){ // 书籍信息的每一项内容对应一列 td var td = document.createElement('td'); // 为td标签加上class属性,值为其键 td.setAttribute('class', key); // 为td标签加上文本, 即其值 td.innerText = val; td.setAttribute('width', 100); // 每次创建一个td标签都添加到tr上 tr.appendChild(td); }); // 将tr标签加入table中 table.appendChild(tr); table.setAttribute('border', 1); // 将table加入展示柜中 $('.container').append(table); }); }else{ alert(ret.message); } } }) } </script> </html>
后端代码(views.py)
from django.shortcuts import render from django.shortcuts import HttpResponse from app01 import models # Create your views here. # 业务处理逻辑 import json from decimal import Decimal from datetime import date class DecimalDatetimeEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, Decimal): return str(o) elif isinstance(o, date): return o.strftime('%Y-%m-%d') return json.JSONEncoder.default(self, o) def index(request): # 定义要给前端传递的字典 post_ret_dict = {'status': True, 'content': None} if request.method == 'POST': try: # 获取前端抛来的字符串 post_data_str = request.POST.get('post_data', None) # 反序列化 post_data_dict = json.loads(post_data_str) # post_data_dict: {'name': ['nameA', 'nameB'],'price':[20,30,40] } from django.db.models import Q # 构造Q搜索 condition = Q() for k, v in post_data_dict.items(): # 同一搜索框内(同一条件)的输入是OR关系(主关系) q = Q() q.connector = 'OR' for item in v: # 这里的k就是前端传来的name属性的值,也就是搜索条件(子关系) q.children.append((k, item)) condition.add(q, 'AND') # 将Q搜索直接作为filter的条件传入,并再用values方法取到想要的值 # 这里用book_type__caption双下划线的形式找到外键表的caption域的值 # 返回值仍是Queryset对象,可通过list转换为列表 list_ret = list(models.Book.objects.filter(condition).values('name', 'pages', 'price', 'pubdate', 'book_type__caption')) # QuerySet post_ret_dict['content'] = list_ret except Exception as e: post_ret_dict['status'] = False # 最后再序列化要给前端的内容 # 注意价格是Decimal类型,印刷日期是Datetime类型,这两类都不是python内置类型,无法直接用json.dumps方法序列化 # 这里可以借助第二个参数,构造一个自定义的解析类,自行处理 post_ret_str = json.dumps(post_ret_dict, cls=DecimalDatetimeEncoder) return HttpResponse(post_ret_str) # django提供的序列化方法,但是无法获得外键表的对应值,不能在使用values方法后序列化 # from django.core.serializers import serialize # ret = models.Book.objects.filter(condition) # QuerySet # str_ret = serialize('json', ret) # print(str_ret) # return HttpResponse(str_ret) return render(request, "index.html")