blob: 87ca9bd83d548d0b48d77ae294ae43777e4c710a [file] [log] [blame]
marinationea036e42022-04-04 11:07:53 +05301# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
Hussain Nagaria8e55c952021-04-26 07:01:06 +05302# License: GNU General Public License v3. See license.txt
3
marination97e3a852022-04-04 11:32:49 +05304import json
5
Hussain Nagaria8e55c952021-04-26 07:01:06 +05306import frappe
marinationea036e42022-04-04 11:07:53 +05307from frappe import _
Hussain Nagariaffc86162021-04-29 20:47:32 +05308from frappe.utils.redis_wrapper import RedisWrapper
marinationea036e42022-04-04 11:07:53 +05309from redis import ResponseError
Ankush Menat4a38ce62022-08-22 00:23:22 +053010from redis.commands.search.field import TagField, TextField
11from redis.commands.search.indexDefinition import IndexDefinition
12from redis.commands.search.suggestion import Suggestion
Hussain Nagaria8e55c952021-04-26 07:01:06 +053013
Ankush Menat494bd9e2022-03-28 18:52:46 +053014WEBSITE_ITEM_INDEX = "website_items_index"
15WEBSITE_ITEM_KEY_PREFIX = "website_item:"
16WEBSITE_ITEM_NAME_AUTOCOMPLETE = "website_items_name_dict"
17WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = "website_items_category_dict"
18
Hussain Nagaria8e55c952021-04-26 07:01:06 +053019
marinationbbcbcf72021-09-02 14:07:59 +053020def get_indexable_web_fields():
21 "Return valid fields from Website Item that can be searched for."
22 web_item_meta = frappe.get_meta("Website Item", cached=True)
23 valid_fields = filter(
24 lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"),
Ankush Menat494bd9e2022-03-28 18:52:46 +053025 web_item_meta.fields,
26 )
marinationbbcbcf72021-09-02 14:07:59 +053027
28 return [df.fieldname for df in valid_fields]
29
Ankush Menat494bd9e2022-03-28 18:52:46 +053030
marination7e207c82022-03-31 16:29:18 +053031def is_redisearch_enabled():
32 "Return True only if redisearch is loaded and enabled."
33 is_redisearch_enabled = frappe.db.get_single_value("E Commerce Settings", "is_redisearch_enabled")
34 return is_search_module_loaded() and is_redisearch_enabled
35
36
marinationb0d7e322021-06-01 12:44:49 +053037def is_search_module_loaded():
Maricad637f792021-09-18 14:23:44 +053038 try:
39 cache = frappe.cache()
Ankush Menat4a38ce62022-08-22 00:23:22 +053040 for module in cache.module_list():
41 if module.get(b"name") == b"search":
42 return True
Maricad637f792021-09-18 14:23:44 +053043 except Exception:
marinationea036e42022-04-04 11:07:53 +053044 return False # handling older redis versions
marinationb0d7e322021-06-01 12:44:49 +053045
Ankush Menat494bd9e2022-03-28 18:52:46 +053046
marination7e207c82022-03-31 16:29:18 +053047def if_redisearch_enabled(function):
48 "Decorator to check if Redisearch is enabled."
Ankush Menat494bd9e2022-03-28 18:52:46 +053049
marinationb0d7e322021-06-01 12:44:49 +053050 def wrapper(*args, **kwargs):
marination7e207c82022-03-31 16:29:18 +053051 if is_redisearch_enabled():
marinationb0d7e322021-06-01 12:44:49 +053052 func = function(*args, **kwargs)
53 return func
54 return
55
56 return wrapper
57
Ankush Menat494bd9e2022-03-28 18:52:46 +053058
marinationb0d7e322021-06-01 12:44:49 +053059def make_key(key):
Ankush Menat4a38ce62022-08-22 00:23:22 +053060 return frappe.cache().make_key(key)
Ankush Menat494bd9e2022-03-28 18:52:46 +053061
marinationb0d7e322021-06-01 12:44:49 +053062
marination7e207c82022-03-31 16:29:18 +053063@if_redisearch_enabled
Hussain Nagaria8e55c952021-04-26 07:01:06 +053064def create_website_items_index():
marinationbbcbcf72021-09-02 14:07:59 +053065 "Creates Index Definition."
66
Ankush Menat4a38ce62022-08-22 00:23:22 +053067 redis = frappe.cache()
68 index = redis.ft(WEBSITE_ITEM_INDEX)
Hussain Nagaria8e55c952021-04-26 07:01:06 +053069
Hussain Nagaria8e55c952021-04-26 07:01:06 +053070 try:
Ankush Menat4a38ce62022-08-22 00:23:22 +053071 index.dropindex() # drop if already exists
marinationea036e42022-04-04 11:07:53 +053072 except ResponseError:
73 # will most likely raise a ResponseError if index does not exist
74 # ignore and create index
Hussain Nagaria8e55c952021-04-26 07:01:06 +053075 pass
marinationea036e42022-04-04 11:07:53 +053076 except Exception:
77 raise_redisearch_error()
Hussain Nagaria8e55c952021-04-26 07:01:06 +053078
Hussain Nagariaffc86162021-04-29 20:47:32 +053079 idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
Hussain Nagaria8e55c952021-04-26 07:01:06 +053080
marinationea036e42022-04-04 11:07:53 +053081 # Index fields mentioned in e-commerce settings
Ankush Menat494bd9e2022-03-28 18:52:46 +053082 idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
83 idx_fields = idx_fields.split(",") if idx_fields else []
Hussain Nagaria8e55c952021-04-26 07:01:06 +053084
Ankush Menat494bd9e2022-03-28 18:52:46 +053085 if "web_item_name" in idx_fields:
86 idx_fields.remove("web_item_name")
marinationb0d7e322021-06-01 12:44:49 +053087
Ankush Menat4a38ce62022-08-22 00:23:22 +053088 idx_fields = [to_search_field(f) for f in idx_fields]
Hussain Nagaria8e55c952021-04-26 07:01:06 +053089
Ankush Menat4a38ce62022-08-22 00:23:22 +053090 # TODO: sortable?
91 index.create_index(
Hussain Nagaria8e55c952021-04-26 07:01:06 +053092 [TextField("web_item_name", sortable=True)] + idx_fields,
Hussain Nagariaffc86162021-04-29 20:47:32 +053093 definition=idx_def,
Hussain Nagaria8e55c952021-04-26 07:01:06 +053094 )
95
96 reindex_all_web_items()
97 define_autocomplete_dictionary()
98
Ankush Menat494bd9e2022-03-28 18:52:46 +053099
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530100def to_search_field(field):
101 if field == "tags":
102 return TagField("tags", separator=",")
103
104 return TextField(field)
105
Ankush Menat494bd9e2022-03-28 18:52:46 +0530106
marination7e207c82022-03-31 16:29:18 +0530107@if_redisearch_enabled
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530108def insert_item_to_index(website_item_doc):
109 # Insert item to index
110 key = get_cache_key(website_item_doc.name)
marinationb0d7e322021-06-01 12:44:49 +0530111 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530112 web_item = create_web_item_map(website_item_doc)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530113
marinationea036e42022-04-04 11:07:53 +0530114 for field, value in web_item.items():
115 super(RedisWrapper, cache).hset(make_key(key), field, value)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530116
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530117 insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
118
Ankush Menat494bd9e2022-03-28 18:52:46 +0530119
marination7e207c82022-03-31 16:29:18 +0530120@if_redisearch_enabled
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530121def insert_to_name_ac(web_name, doc_name):
Ankush Menat4a38ce62022-08-22 00:23:22 +0530122 ac = frappe.cache().ft()
123 ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(web_name, payload=doc_name))
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530124
Ankush Menat494bd9e2022-03-28 18:52:46 +0530125
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530126def create_web_item_map(website_item_doc):
127 fields_to_index = get_fields_indexed()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530128 web_item = {}
129
marinationea036e42022-04-04 11:07:53 +0530130 for field in fields_to_index:
131 web_item[field] = website_item_doc.get(field) or ""
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530132
133 return web_item
Hussain Nagariabd0d0ad2021-05-26 20:26:34 +0530134
Ankush Menat494bd9e2022-03-28 18:52:46 +0530135
marination7e207c82022-03-31 16:29:18 +0530136@if_redisearch_enabled
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530137def update_index_for_item(website_item_doc):
138 # Reinsert to Cache
139 insert_item_to_index(website_item_doc)
140 define_autocomplete_dictionary()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530141
Ankush Menat494bd9e2022-03-28 18:52:46 +0530142
marination7e207c82022-03-31 16:29:18 +0530143@if_redisearch_enabled
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530144def delete_item_from_index(website_item_doc):
marinationb0d7e322021-06-01 12:44:49 +0530145 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530146 key = get_cache_key(website_item_doc.name)
marinationb0d7e322021-06-01 12:44:49 +0530147
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530148 try:
marinationb0d7e322021-06-01 12:44:49 +0530149 cache.delete(key)
marinationea30ce42021-06-02 13:24:06 +0530150 except Exception:
marinationea036e42022-04-04 11:07:53 +0530151 raise_redisearch_error()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530152
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530153 delete_from_ac_dict(website_item_doc)
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530154 return True
155
Ankush Menat494bd9e2022-03-28 18:52:46 +0530156
marination7e207c82022-03-31 16:29:18 +0530157@if_redisearch_enabled
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530158def delete_from_ac_dict(website_item_doc):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530159 """Removes this items's name from autocomplete dictionary"""
Ankush Menat4a38ce62022-08-22 00:23:22 +0530160 ac = frappe.cache().ft()
161 ac.sugdel(website_item_doc.web_item_name)
Hussain Nagaria34f3a662021-05-05 13:47:43 +0530162
Ankush Menat494bd9e2022-03-28 18:52:46 +0530163
marination7e207c82022-03-31 16:29:18 +0530164@if_redisearch_enabled
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530165def define_autocomplete_dictionary():
marination07f17452022-04-01 18:47:01 +0530166 """
167 Defines/Redefines an autocomplete search dictionary for Website Item Name.
168 Also creats autocomplete dictionary for Published Item Groups.
169 """
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530170
marinationb0d7e322021-06-01 12:44:49 +0530171 cache = frappe.cache()
marinationb0d7e322021-06-01 12:44:49 +0530172
Hussain Nagariaf3421552021-04-26 11:15:20 +0530173 # Delete both autocomplete dicts
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530174 try:
marinationb0d7e322021-06-01 12:44:49 +0530175 cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE))
176 cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE))
marinationea30ce42021-06-02 13:24:06 +0530177 except Exception:
marinationea036e42022-04-04 11:07:53 +0530178 raise_redisearch_error()
marinationb0d7e322021-06-01 12:44:49 +0530179
Ankush Menat4a38ce62022-08-22 00:23:22 +0530180 create_items_autocomplete_dict()
181 create_item_groups_autocomplete_dict()
marination07f17452022-04-01 18:47:01 +0530182
183
184@if_redisearch_enabled
Ankush Menat4a38ce62022-08-22 00:23:22 +0530185def create_items_autocomplete_dict():
marination07f17452022-04-01 18:47:01 +0530186 "Add items as suggestions in Autocompleter."
Ankush Menat4a38ce62022-08-22 00:23:22 +0530187
188 ac = frappe.cache().ft()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530189 items = frappe.get_all(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530190 "Website Item", fields=["web_item_name", "item_group"], filters={"published": 1}
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530191 )
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530192 for item in items:
Ankush Menat4a38ce62022-08-22 00:23:22 +0530193 ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(item.web_item_name))
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530194
marination07f17452022-04-01 18:47:01 +0530195
196@if_redisearch_enabled
Ankush Menat4a38ce62022-08-22 00:23:22 +0530197def create_item_groups_autocomplete_dict():
marination07f17452022-04-01 18:47:01 +0530198 "Add item groups with weightage as suggestions in Autocompleter."
Ankush Menat4a38ce62022-08-22 00:23:22 +0530199
marination07f17452022-04-01 18:47:01 +0530200 published_item_groups = frappe.get_all(
201 "Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1}
202 )
203 if not published_item_groups:
204 return
205
Ankush Menat4a38ce62022-08-22 00:23:22 +0530206 ac = frappe.cache().ft()
207
marination07f17452022-04-01 18:47:01 +0530208 for item_group in published_item_groups:
marination34456822022-04-04 12:33:25 +0530209 payload = json.dumps({"name": item_group.name, "route": item_group.route})
Ankush Menat4a38ce62022-08-22 00:23:22 +0530210 ac.sugadd(
211 WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
marination07f17452022-04-01 18:47:01 +0530212 Suggestion(
213 string=item_group.name,
marination7ef1ccb2022-04-04 12:04:35 +0530214 score=frappe.utils.flt(item_group.weightage) or 1.0,
marination07f17452022-04-01 18:47:01 +0530215 payload=payload, # additional info that can be retrieved later
Ankush Menat4a38ce62022-08-22 00:23:22 +0530216 ),
marination07f17452022-04-01 18:47:01 +0530217 )
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530218
Ankush Menat494bd9e2022-03-28 18:52:46 +0530219
marination7e207c82022-03-31 16:29:18 +0530220@if_redisearch_enabled
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530221def reindex_all_web_items():
Ankush Menat494bd9e2022-03-28 18:52:46 +0530222 items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True})
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530223
marinationb0d7e322021-06-01 12:44:49 +0530224 cache = frappe.cache()
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530225 for item in items:
226 web_item = create_web_item_map(item)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530227 key = make_key(get_cache_key(item.name))
228
marinationea036e42022-04-04 11:07:53 +0530229 for field, value in web_item.items():
230 super(RedisWrapper, cache).hset(key, field, value)
Hussain Nagariaffc86162021-04-29 20:47:32 +0530231
Ankush Menat494bd9e2022-03-28 18:52:46 +0530232
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530233def get_cache_key(name):
234 name = frappe.scrub(name)
235 return f"{WEBSITE_ITEM_KEY_PREFIX}{name}"
236
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530237
Ankush Menat494bd9e2022-03-28 18:52:46 +0530238def get_fields_indexed():
239 fields_to_index = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
240 fields_to_index = fields_to_index.split(",") if fields_to_index else []
241
242 mandatory_fields = ["name", "web_item_name", "route", "thumbnail", "ranking"]
Hussain Nagaria8e55c952021-04-26 07:01:06 +0530243 fields_to_index = fields_to_index + mandatory_fields
244
245 return fields_to_index
marinationea036e42022-04-04 11:07:53 +0530246
247
248def raise_redisearch_error():
249 "Create an Error Log and raise error."
Rushabh Mehta548afba2022-05-02 15:04:26 +0530250 log = frappe.log_error("Redisearch Error")
marinationea036e42022-04-04 11:07:53 +0530251 log_link = frappe.utils.get_link_to_form("Error Log", log.name)
252
253 frappe.throw(
254 msg=_("Something went wrong. Check {0}").format(log_link), title=_("Redisearch Error")
255 )