本節涉及:

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>

index.html

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>

JS程式碼

後端程式碼(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")

views.py