blob: 82829bf1effea766f85d30a3569479335bbb0898 [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
marination9fb61ef2022-02-01 00:39:14 +05306from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField
Hussain Nagaria8e55c952021-04-26 07:01:06 +05307
Ankush Menat494bd9e2022-03-28 18:52:46 +05308WEBSITE_ITEM_INDEX = "website_items_index"
9WEBSITE_ITEM_KEY_PREFIX = "website_item:"
10WEBSITE_ITEM_NAME_AUTOCOMPLETE = "website_items_name_dict"
11WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = "website_items_category_dict"
12
Hussain Nagaria8e55c952021-04-26 07:01:06 +053013
marinationbbcbcf72021-09-02 14:07:59 +053014def get_indexable_web_fields():
15 "Return valid fields from Website Item that can be searched for."
16 web_item_meta = frappe.get_meta("Website Item", cached=True)
17 valid_fields = filter(
18 lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"),
Ankush Menat494bd9e2022-03-28 18:52:46 +053019 web_item_meta.fields,
20 )
marinationbbcbcf72021-09-02 14:07:59 +053021
22 return [df.fieldname for df in valid_fields]
23
Ankush Menat494bd9e2022-03-28 18:52:46 +053024
marinationb0d7e322021-06-01 12:44:49 +053025def is_search_module_loaded():
Maricad637f792021-09-18 14:23:44 +053026 try:
27 cache = frappe.cache()
Ankush Menat494bd9e2022-03-28 18:52:46 +053028 out = cache.execute_command("MODULE LIST")
marinationb0d7e322021-06-01 12:44:49 +053029
Maricad637f792021-09-18 14:23:44 +053030 parsed_output = " ".join(
31 (" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
32 )
33 return "search" in parsed_output
34 except Exception:
35 return False
marinationb0d7e322021-06-01 12:44:49 +053036
Ankush Menat494bd9e2022-03-28 18:52:46 +053037
marinationb0d7e322021-06-01 12:44:49 +053038def if_redisearch_loaded(function):
marinationbbcbcf72021-09-02 14:07:59 +053039 "Decorator to check if Redisearch is loaded."
Ankush Menat494bd9e2022-03-28 18:52:46 +053040
marinationb0d7e322021-06-01 12:44:49 +053041 def wrapper(*args, **kwargs):
42 if is_search_module_loaded():
43 func = function(*args, **kwargs)
44 return func
45 return
46
47 return wrapper
48
Ankush Menat494bd9e2022-03-28 18:52:46 +053049
marinationb0d7e322021-06-01 12:44:49 +053050def make_key(key):
Ankush Menat494bd9e2022-03-28 18:52:46 +053051 return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8")
52
marinationb0d7e322021-06-01 12:44:49 +053053
54@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +053055def create_website_items_index():
marinationbbcbcf72021-09-02 14:07:59 +053056 "Creates Index Definition."
57
Hussain Nagaria8e55c952021-04-26 07:01:06 +053058 # CREATE index
Hussain Nagariaffc86162021-04-29 20:47:32 +053059 client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
Hussain Nagaria8e55c952021-04-26 07:01:06 +053060
61 # DROP if already exists
62 try:
63 client.drop_index()
marinationb0d7e322021-06-01 12:44:49 +053064 except Exception:
Hussain Nagaria8e55c952021-04-26 07:01:06 +053065 pass
66
Hussain Nagariaffc86162021-04-29 20:47:32 +053067 idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
Hussain Nagaria8e55c952021-04-26 07:01:06 +053068
69 # Based on e-commerce settings
Ankush Menat494bd9e2022-03-28 18:52:46 +053070 idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
71 idx_fields = idx_fields.split(",") if idx_fields else []
Hussain Nagaria8e55c952021-04-26 07:01:06 +053072
Ankush Menat494bd9e2022-03-28 18:52:46 +053073 if "web_item_name" in idx_fields:
74 idx_fields.remove("web_item_name")
marinationb0d7e322021-06-01 12:44:49 +053075
Hussain Nagaria8e55c952021-04-26 07:01:06 +053076 idx_fields = list(map(to_search_field, idx_fields))
77
78 client.create_index(
79 [TextField("web_item_name", sortable=True)] + idx_fields,
Hussain Nagariaffc86162021-04-29 20:47:32 +053080 definition=idx_def,
Hussain Nagaria8e55c952021-04-26 07:01:06 +053081 )
82
83 reindex_all_web_items()
84 define_autocomplete_dictionary()
85
Ankush Menat494bd9e2022-03-28 18:52:46 +053086
Hussain Nagaria8e55c952021-04-26 07:01:06 +053087def to_search_field(field):
88 if field == "tags":
89 return TagField("tags", separator=",")
90
91 return TextField(field)
92
Ankush Menat494bd9e2022-03-28 18:52:46 +053093
marinationb0d7e322021-06-01 12:44:49 +053094@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +053095def insert_item_to_index(website_item_doc):
96 # Insert item to index
97 key = get_cache_key(website_item_doc.name)
marinationb0d7e322021-06-01 12:44:49 +053098 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +053099 web_item = create_web_item_map(website_item_doc)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530100
101 for k, v in web_item.items():
marinationb0d7e322021-06-01 12:44:49 +0530102 super(RedisWrapper, cache).hset(make_key(key), k, v)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530103
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530104 insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
105
Ankush Menat494bd9e2022-03-28 18:52:46 +0530106
marinationb0d7e322021-06-01 12:44:49 +0530107@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530108def insert_to_name_ac(web_name, doc_name):
Hussain Nagariaffc86162021-04-29 20:47:32 +0530109 ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530110 ac.add_suggestions(Suggestion(web_name, payload=doc_name))
111
Ankush Menat494bd9e2022-03-28 18:52:46 +0530112
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530113def create_web_item_map(website_item_doc):
114 fields_to_index = get_fields_indexed()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530115 web_item = {}
116
117 for f in fields_to_index:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530118 web_item[f] = website_item_doc.get(f) or ""
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530119
120 return web_item
Hussain Nagariabd0d0ad2021-05-26 20:26:34 +0530121
Ankush Menat494bd9e2022-03-28 18:52:46 +0530122
marinationb0d7e322021-06-01 12:44:49 +0530123@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530124def update_index_for_item(website_item_doc):
125 # Reinsert to Cache
126 insert_item_to_index(website_item_doc)
127 define_autocomplete_dictionary()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530128
Ankush Menat494bd9e2022-03-28 18:52:46 +0530129
marinationb0d7e322021-06-01 12:44:49 +0530130@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530131def delete_item_from_index(website_item_doc):
marinationb0d7e322021-06-01 12:44:49 +0530132 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530133 key = get_cache_key(website_item_doc.name)
marinationb0d7e322021-06-01 12:44:49 +0530134
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530135 try:
marinationb0d7e322021-06-01 12:44:49 +0530136 cache.delete(key)
marinationea30ce42021-06-02 13:24:06 +0530137 except Exception:
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530138 return False
139
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530140 delete_from_ac_dict(website_item_doc)
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530141 return True
142
Ankush Menat494bd9e2022-03-28 18:52:46 +0530143
marinationb0d7e322021-06-01 12:44:49 +0530144@if_redisearch_loaded
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530145def delete_from_ac_dict(website_item_doc):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530146 """Removes this items's name from autocomplete dictionary"""
marinationb0d7e322021-06-01 12:44:49 +0530147 cache = frappe.cache()
148 name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530149 name_ac.delete(website_item_doc.web_item_name)
150
Ankush Menat494bd9e2022-03-28 18:52:46 +0530151
marinationb0d7e322021-06-01 12:44:49 +0530152@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530153def define_autocomplete_dictionary():
Hussain Nagariaf3421552021-04-26 11:15:20 +0530154 """Creates an autocomplete search dictionary for `name`.
Ankush Menat494bd9e2022-03-28 18:52:46 +0530155 Also creats autocomplete dictionary for `categories` if
156 checked in E Commerce Settings"""
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530157
marinationb0d7e322021-06-01 12:44:49 +0530158 cache = frappe.cache()
159 name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
160 cat_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530161
Hussain Nagariaf3421552021-04-26 11:15:20 +0530162 ac_categories = frappe.db.get_single_value(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530163 "E Commerce Settings", "show_categories_in_search_autocomplete"
Hussain Nagariaf3421552021-04-26 11:15:20 +0530164 )
marinationb0d7e322021-06-01 12:44:49 +0530165
Hussain Nagariaf3421552021-04-26 11:15:20 +0530166 # Delete both autocomplete dicts
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530167 try:
marinationb0d7e322021-06-01 12:44:49 +0530168 cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE))
169 cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE))
marinationea30ce42021-06-02 13:24:06 +0530170 except Exception:
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530171 return False
marinationb0d7e322021-06-01 12:44:49 +0530172
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530173 items = frappe.get_all(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530174 "Website Item", fields=["web_item_name", "item_group"], filters={"published": 1}
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530175 )
176
177 for item in items:
Hussain Nagariaf3421552021-04-26 11:15:20 +0530178 name_ac.add_suggestions(Suggestion(item.web_item_name))
179 if ac_categories and item.item_group:
180 cat_ac.add_suggestions(Suggestion(item.item_group))
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530181
182 return True
183
Ankush Menat494bd9e2022-03-28 18:52:46 +0530184
marinationb0d7e322021-06-01 12:44:49 +0530185@if_redisearch_loaded
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530186def reindex_all_web_items():
Ankush Menat494bd9e2022-03-28 18:52:46 +0530187 items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True})
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530188
marinationb0d7e322021-06-01 12:44:49 +0530189 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530190 for item in items:
191 web_item = create_web_item_map(item)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530192 key = make_key(get_cache_key(item.name))
193
194 for k, v in web_item.items():
marinationb0d7e322021-06-01 12:44:49 +0530195 super(RedisWrapper, cache).hset(key, k, v)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530196
Ankush Menat494bd9e2022-03-28 18:52:46 +0530197
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530198def get_cache_key(name):
199 name = frappe.scrub(name)
200 return f"{WEBSITE_ITEM_KEY_PREFIX}{name}"
201
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530202
Ankush Menat494bd9e2022-03-28 18:52:46 +0530203def get_fields_indexed():
204 fields_to_index = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
205 fields_to_index = fields_to_index.split(",") if fields_to_index else []
206
207 mandatory_fields = ["name", "web_item_name", "route", "thumbnail", "ranking"]
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530208 fields_to_index = fields_to_index + mandatory_fields
209
210 return fields_to_index
211
Ankush Menat494bd9e2022-03-28 18:52:46 +0530212
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530213# TODO: Remove later
214# # Figure out a way to run this at startup
215define_autocomplete_dictionary()
216create_website_items_index()