混合搜索在各大網站如京東、淘寶都有應用,他們的原理都是什么呢?本博文將為你介紹它們的實現過程。
混合搜索的原理,用一句話來說就是:關鍵字id進行拼接。
混合搜索示例:

數據庫設計:
視頻方向:
class Direction(models.Model):
weight = models.IntegerField(verbose_name='權重(按從大到小排列)', default=0)
name = models.CharField(verbose_name='名稱', max_length=32)
classification = models.ManyToManyField('Classification')
class Meta:
db_table = 'Direction'
verbose_name_plural = u'方向(視頻方向)'
def __str__(self):
return self.name
視頻分類:
class Classification(models.Model):
weight = models.IntegerField(verbose_name='權重(按從大到小排列)', default=0)
name = models.CharField(verbose_name='名稱', max_length=32)
class Meta:
db_table = 'Classification'
verbose_name_plural = u'分類(視頻分類)'
def __str__(self):
return self.name
視頻:
class Video(models.Model):
status_choice = (
(0, u'下線'),
(1, u'上線'),
)
level_choice = (
(1, u'初級'),
(2, u'中級'),
(3, u'高級'),
)
status = models.IntegerField(verbose_name='狀態', choices=status_choice, default=1)#可用於管理員的審核
level = models.IntegerField(verbose_name='級別', choices=level_choice, default=1)
classification = models.ForeignKey('Classification', null=True, blank=True)
weight = models.IntegerField(verbose_name='權重(按從大到小排列)', default=0)
title = models.CharField(verbose_name='標題', max_length=32)
summary = models.CharField(verbose_name='簡介', max_length=32)
img = models.ImageField(verbose_name='圖片', upload_to='./static/images/Video/')
href = models.CharField(verbose_name='視頻地址', max_length=256)
create_date = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'Video'
verbose_name_plural = u'視頻'
def __str__(self):
return self.title
備注:
- 視頻方向Direction類和視頻分類Classification多對多關系,即一個視頻方向對應多個視頻分類,一個視頻分類也可以對應多個視頻方向。——classification = models.ManyToManyField('Classification')
- 視頻分類Classification類和視頻Video類是一對多關系,即一個視頻分類對應多個視頻
- 視頻Video類中level_choice 與視頻也是一對多關系,這里為了簡化表關系,直接使用choices=level_choice來代替
混合搜索url設計:
默認url:

http://127.0.0.1:8000/video-0-0-0.html,其中第一個數字代表視頻方向,默認0代表全部方向;第二個數字代表視頻分類,默認0代表全部分類;第三個數字代表視頻等級,默認0代表全部等級。
每一個a標簽默認的url:
例如運維自動化:<a href="/video-1-0-0.html">運維自動化</a>,即視頻方向的對應數字為1,視頻分類和視頻等級都為0,這樣做的目的是為了將此url和用戶當前url進行拼接,並跳轉到新的url。
選擇運維自動化后的url:

多選情況下的url:

前端html:
加載自定義simple_tag:
{% load xx %}
注:
- xx:名為xx的py文件,里面包含自定義函數,方便html中進行調用
- 在app中創建templatetags文件夾,將xx.py文件放在templatetags文件夾下
關於自定義simple_tag的更多信息,詳見下文。
css:
<style> a{ display: inline-block; padding: 8px; } .active{ background-color: coral; color: white; } .item{ display: inline-block; width: 300px; height: 400px; } .item img{ border: 0; width: 300px; height: 280px; overflow: hidden; } </style>
設置css目的,當用戶選擇視頻方向、視頻分類、視頻等級時,加深對應a標簽。
選擇區域html:
<h3>選擇:</h3>
<div>
{% action_all current_url 1 %} :
{% for item in direction_list %}
{% action current_url item 1 %}
{% endfor %}
</div>
<div>
{% action_all current_url 2 %} :
{% for item in class_list %}
{% action current_url item 2 %}
{% endfor %}
</div>
<div>
{% action_all current_url 3 %} :
{% for item in level_list %}
{% action current_url item 3 %}
{% endfor %}
</div>
<hr />
該區域全部基於自定義simple_tag實現,詳見下文。
視頻顯示區域html:
<h3>視頻:</h3>
<hr />
{% for item in video_list %}
<a class="item" href="{{ item.href }}">
<img src="/{{ item.img }}">
<p>{{ item.title }}</p>
<p>{{ item.summary }}</p>
</a>
{% endfor %}
循環顯示符合條件的全部視頻。
自定義simple_tag:
全部標簽的生成:
我們希望,當用戶選擇全部標簽時,url對應位置為0,即當用戶三個選擇都是全部時,url為:/video-0-0-0.html
以視頻方向為例介紹:
對應位置html:
{% action_all current_url 1 %} :
從上述html可看出,action_all為對應的函數,它接收兩個參數:當前url(current_url)、當前位置(視頻方向、視頻分類、視頻等級)。其中current_url是后台傳入html的,詳見下文后台views函數介紹。
action_all函數:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe
@register.simple_tag #注冊simple_tag
def action_all(current_url,index): #接收當前url和對應的位置參數
"""
獲取當前url,video-1-1-2.html
:param current_url:
:param item:
:return:
"""
url_part_list = current_url.split('-') #根據“-”進行分割
if index == 3: #如果是視頻等級
if url_part_list[index] == "0.html": #如果選擇的是全部
temp = "<a href='%s' class='active'>全部</a>" #添加 “active”屬性
else:
temp = "<a href='%s'>全部</a>"
url_part_list[index] = "0.html"
else:
if url_part_list[index] == "0":
temp = "<a href='%s' class='active'>全部</a>"
else:
temp = "<a href='%s'>全部</a>"
url_part_list[index] = "0"
href = '-'.join(url_part_list) #處理后的列表再拼接成url字符串
temp = temp % (href,) #生成對應的a標簽
return mark_safe(temp) #返回原生html
其它a標簽:
以視頻方向為例介紹:
對應位置html:
{% action current_url item 1 %}
從上述html可看出:action函數接收三個參數 當前url、當前標簽對象、當前位置。
action函數:
@register.simple_tag
def action(current_url, item,index):
# videos-0-0-1.html
# item: id name
# video- 2 -0-0.html
url_part_list = current_url.split('-')
if index == 3:
if str(item['id']) == url_part_list[3].split('.')[0]: #如果當前標簽被選中
temp = "<a href='%s' class='active'>%s</a>"
else:
temp = "<a href='%s'>%s</a>"
url_part_list[index] = str(item['id']) + '.html' #拼接對應位置的部分url
else:
if str(item['id']) == url_part_list[index]:
temp = "<a href='%s' class='active'>%s</a>"
else:
temp = "<a href='%s'>%s</a>"
url_part_list[index] = str(item['id'])
ur_str = '-'.join(url_part_list) #拼接整體url
temp = temp %(ur_str, item['name']) #生成對應的a標簽
return mark_safe(temp) #返回安全的html
至此,所有選擇標簽生成完畢,能夠根據用戶選擇動態生成url。
視頻顯示區域的前后端處理:
前端html:
{% for item in video_list %}
<a class="item" href="{{ item.href }}">
<img src="/{{ item.img }}">
<p>{{ item.title }}</p>
<p>{{ item.summary }}</p>
</a>
{% endfor %}
循環顯示所有符合條件的視頻。
后端views函數:
def video(request,*args,**kwargs):
print(kwargs)
print(request.path)
request_path = request.path #當前請求的路徑
q = {} #從數據庫獲取視頻時的filter條件字典
q['status'] = 1 #狀態為審核通過的
class_id = int(kwargs.get('classification_id')) #獲取url中的視頻分類id
direction_list = models.Direction.objects.all().values('id','name') #從數據庫中獲取所有的視頻方向(包括視頻方向的id和name)
if kwargs.get('direction_id') == '0':
# 方向選擇全部
print('方向等於0')
class_list = models.Classification.objects.all().values('id', 'name') #方向id=0,即獲取所有的視頻分類(包括視頻分類的id和name)
if kwargs.get('classification_id') == '0': #如果視頻分類id也為0,即全部分類
pass
else:
# 如果視頻分類不是全部,過濾條件為視頻分類id in [url中的視頻分類id]
q['classification_id__in'] = [class_id,]
else:
print('方向不為0')
# 方向選擇某一個方向,
# 如果分類是0
if kwargs.get('classification_id') == '0':
print('分類為0')
obj = models.Direction.objects.get(id=int(kwargs.get('direction_id'))) #獲取已選擇的視頻方向
class_list = obj.classification.all().values('id', 'name') #獲取該方向的所有視頻分類
id_list = list(map(lambda x: x['id'], class_list)) #獲取所有視頻分類對應的視頻分類id
q['classification_id__in'] = id_list #過濾條件為視頻分類id in [該方向下的所有視頻分類id]
else:
#方向不為0,分類也不為0
obj = models.Direction.objects.get(id=int(kwargs.get('direction_id')))
class_list = obj.classification.all().values('id', 'name')
id_list = list(map(lambda x:x['id'], class_list))
q['classification_id__in'] = [class_id,] #過濾條件為視頻分類id in [已經選擇的視頻分類id]
print('分類不為0')
# 當前分類如果在獲取的所有分類中,則方向下的所有相關分類顯示
# 當前分類如果不在獲取的所有分類中,
if int(kwargs.get('classification_id')) in id_list:
pass
else:
print('不再,獲取指定方向下的所有分類:選中的回到全部')
url_part_list = request_path.split('-')
url_part_list[2] = '0'
request_path = '-'.join(url_part_list)
level_id = int(kwargs.get('level_id')) #視頻等級id
if level_id == 0:
pass
else:
q['level'] = level_id #過濾條件增加視頻等級
# models.Video.objects.filter(status=1)
video_list = models.Video.objects.filter(**q).values('title','summary', 'img', 'href')
# level_list = models.Video.level_choice
ret = map(lambda x:{"id": x[0], 'name': x[1]}, models.Video.level_choice)#把視頻等級轉化為單個標簽是字典格式,整體是列表格式
level_list = list(ret)
return render(request, 'video.html', {'direction_list': direction_list,
'class_list': class_list,
'level_list': level_list,
'current_url': request_path,
"video_list": video_list})
總結:以上就是混合搜索的前后端全過程,歡迎讀者來與樓主進行交流。如果本文對您有參考價值,歡迎幫博主點下文章下方的推薦,謝謝!
