blob: f66d2ef0e857b3629d4dbf79a6b711e7afc59ae0 [file] [log] [blame]
marinationb0d7e322021-06-01 12:44:49 +05301# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
Hussain Nagaria8e55c952021-04-26 07:01:06 +05302# License: GNU General Public License v3. See license.txt
3
Hussain Nagaria8e55c952021-04-26 07:01:06 +05304import frappe
Hussain Nagariaffc86162021-04-29 20:47:32 +05305from frappe.utils.redis_wrapper import RedisWrapper
marinationb0d7e322021-06-01 12:44:49 +05306from redisearch import (Client, AutoCompleter, Suggestion, IndexDefinition, TextField, TagField)
Hussain Nagaria8e55c952021-04-26 07:01:06 +05307
Hussain Nagaria8e55c952021-04-26 07:01:06 +05308WEBSITE_ITEM_INDEX = 'website_items_index'
9WEBSITE_ITEM_KEY_PREFIX = 'website_item:'
10WEBSITE_ITEM_NAME_AUTOCOMPLETE = 'website_items_name_dict'
Hussain Nagariaf3421552021-04-26 11:15:20 +053011WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict'
Hussain Nagaria8e55c952021-04-26 07:01:06 +053012
13ALLOWED_INDEXABLE_FIELDS_SET = {
marination2fec0682021-07-13 23:46:24 +053014 'web_item_name',
Hussain Nagaria8e55c952021-04-26 07:01:06 +053015 'item_code',
16 'item_name',
17 'item_group',
18 'brand',
19 'description',
20 'web_long_description'
21}
22
marinationb0d7e322021-06-01 12:44:49 +053023def is_search_module_loaded():
24 cache = frappe.cache()
25 out = cache.execute_command('MODULE LIST')
26
27 parsed_output = " ".join(
28 (" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
29 )
30
31 return "search" in parsed_output
32
33# Decorator for checking wether Redisearch is there or not
34def if_redisearch_loaded(function):
35 def wrapper(*args, **kwargs):
36 if is_search_module_loaded():
37 func = function(*args, **kwargs)
38 return func
39 return
40
41 return wrapper
42
43def make_key(key):
44 return "{0}|{1}".format(frappe.conf.db_name, key).encode('utf-8')
45
46@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +053047def create_website_items_index():
48 '''Creates Index Definition'''
49 # CREATE index
Hussain Nagariaffc86162021-04-29 20:47:32 +053050 client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
Hussain Nagaria8e55c952021-04-26 07:01:06 +053051
52 # DROP if already exists
53 try:
54 client.drop_index()
marinationb0d7e322021-06-01 12:44:49 +053055 except Exception:
Hussain Nagaria8e55c952021-04-26 07:01:06 +053056 pass
57
Hussain Nagariaffc86162021-04-29 20:47:32 +053058 idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
Hussain Nagaria8e55c952021-04-26 07:01:06 +053059
60 # Based on e-commerce settings
61 idx_fields = frappe.db.get_single_value(
marinationb0d7e322021-06-01 12:44:49 +053062 'E Commerce Settings',
Hussain Nagaria8e55c952021-04-26 07:01:06 +053063 'search_index_fields'
marination24ba06c2021-06-08 14:47:11 +053064 )
65 idx_fields = idx_fields.split(',') if idx_fields else []
Hussain Nagaria8e55c952021-04-26 07:01:06 +053066
67 if 'web_item_name' in idx_fields:
68 idx_fields.remove('web_item_name')
marinationb0d7e322021-06-01 12:44:49 +053069
Hussain Nagaria8e55c952021-04-26 07:01:06 +053070 idx_fields = list(map(to_search_field, idx_fields))
71
72 client.create_index(
73 [TextField("web_item_name", sortable=True)] + idx_fields,
Hussain Nagariaffc86162021-04-29 20:47:32 +053074 definition=idx_def,
Hussain Nagaria8e55c952021-04-26 07:01:06 +053075 )
76
77 reindex_all_web_items()
78 define_autocomplete_dictionary()
79
80def to_search_field(field):
81 if field == "tags":
82 return TagField("tags", separator=",")
83
84 return TextField(field)
85
marinationb0d7e322021-06-01 12:44:49 +053086@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +053087def insert_item_to_index(website_item_doc):
88 # Insert item to index
89 key = get_cache_key(website_item_doc.name)
marinationb0d7e322021-06-01 12:44:49 +053090 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +053091 web_item = create_web_item_map(website_item_doc)
Hussain Nagariaffc86162021-04-29 20:47:32 +053092
93 for k, v in web_item.items():
marinationb0d7e322021-06-01 12:44:49 +053094 super(RedisWrapper, cache).hset(make_key(key), k, v)
Hussain Nagariaffc86162021-04-29 20:47:32 +053095
Hussain Nagaria8e55c952021-04-26 07:01:06 +053096 insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
97
marinationb0d7e322021-06-01 12:44:49 +053098@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +053099def insert_to_name_ac(web_name, doc_name):
Hussain Nagariaffc86162021-04-29 20:47:32 +0530100 ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530101 ac.add_suggestions(Suggestion(web_name, payload=doc_name))
102
103def create_web_item_map(website_item_doc):
104 fields_to_index = get_fields_indexed()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530105 web_item = {}
106
107 for f in fields_to_index:
108 web_item[f] = website_item_doc.get(f) or ''
109
110 return web_item
Hussain Nagariabd0d0ad2021-05-26 20:26:34 +0530111
marinationb0d7e322021-06-01 12:44:49 +0530112@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530113def update_index_for_item(website_item_doc):
114 # Reinsert to Cache
115 insert_item_to_index(website_item_doc)
116 define_autocomplete_dictionary()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530117
marinationb0d7e322021-06-01 12:44:49 +0530118@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530119def delete_item_from_index(website_item_doc):
marinationb0d7e322021-06-01 12:44:49 +0530120 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530121 key = get_cache_key(website_item_doc.name)
marinationb0d7e322021-06-01 12:44:49 +0530122
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530123 try:
marinationb0d7e322021-06-01 12:44:49 +0530124 cache.delete(key)
marinationea30ce42021-06-02 13:24:06 +0530125 except Exception:
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530126 return False
127
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530128 delete_from_ac_dict(website_item_doc)
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530129 return True
130
marinationb0d7e322021-06-01 12:44:49 +0530131@if_redisearch_loaded
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530132def delete_from_ac_dict(website_item_doc):
133 '''Removes this items's name from autocomplete dictionary'''
marinationb0d7e322021-06-01 12:44:49 +0530134 cache = frappe.cache()
135 name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530136 name_ac.delete(website_item_doc.web_item_name)
137
marinationb0d7e322021-06-01 12:44:49 +0530138@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530139def define_autocomplete_dictionary():
Hussain Nagariaf3421552021-04-26 11:15:20 +0530140 """Creates an autocomplete search dictionary for `name`.
marinationb0d7e322021-06-01 12:44:49 +0530141 Also creats autocomplete dictionary for `categories` if
142 checked in E Commerce Settings"""
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530143
marinationb0d7e322021-06-01 12:44:49 +0530144 cache = frappe.cache()
145 name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
146 cat_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530147
Hussain Nagariaf3421552021-04-26 11:15:20 +0530148 ac_categories = frappe.db.get_single_value(
marinationb0d7e322021-06-01 12:44:49 +0530149 'E Commerce Settings',
Hussain Nagariaf3421552021-04-26 11:15:20 +0530150 'show_categories_in_search_autocomplete'
151 )
marinationb0d7e322021-06-01 12:44:49 +0530152
Hussain Nagariaf3421552021-04-26 11:15:20 +0530153 # Delete both autocomplete dicts
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530154 try:
marinationb0d7e322021-06-01 12:44:49 +0530155 cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE))
156 cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE))
marinationea30ce42021-06-02 13:24:06 +0530157 except Exception:
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530158 return False
marinationb0d7e322021-06-01 12:44:49 +0530159
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530160 items = frappe.get_all(
marinationb0d7e322021-06-01 12:44:49 +0530161 'Website Item',
162 fields=['web_item_name', 'item_group'],
163 filters={"published": 1}
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530164 )
165
166 for item in items:
Hussain Nagariaf3421552021-04-26 11:15:20 +0530167 name_ac.add_suggestions(Suggestion(item.web_item_name))
168 if ac_categories and item.item_group:
169 cat_ac.add_suggestions(Suggestion(item.item_group))
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530170
171 return True
172
marinationb0d7e322021-06-01 12:44:49 +0530173@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530174def reindex_all_web_items():
175 items = frappe.get_all(
marinationb0d7e322021-06-01 12:44:49 +0530176 'Website Item',
177 fields=get_fields_indexed(),
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530178 filters={"published": True}
179 )
180
marinationb0d7e322021-06-01 12:44:49 +0530181 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530182 for item in items:
183 web_item = create_web_item_map(item)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530184 key = make_key(get_cache_key(item.name))
185
186 for k, v in web_item.items():
marinationb0d7e322021-06-01 12:44:49 +0530187 super(RedisWrapper, cache).hset(key, k, v)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530188
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530189def get_cache_key(name):
190 name = frappe.scrub(name)
191 return f"{WEBSITE_ITEM_KEY_PREFIX}{name}"
192
193def get_fields_indexed():
194 fields_to_index = frappe.db.get_single_value(
marinationb0d7e322021-06-01 12:44:49 +0530195 'E Commerce Settings',
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530196 'search_index_fields'
marination24ba06c2021-06-08 14:47:11 +0530197 )
198 fields_to_index = fields_to_index.split(',') if fields_to_index else []
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530199
200 mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail']
201 fields_to_index = fields_to_index + mandatory_fields
202
203 return fields_to_index
204
205# TODO: Remove later
206# # Figure out a way to run this at startup
207define_autocomplete_dictionary()
208create_website_items_index()