marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 1 | # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 2 | # License: GNU General Public License v3. See license.txt |
| 3 | |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 4 | import frappe |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 5 | from frappe.utils.redis_wrapper import RedisWrapper |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 6 | from redisearch import (Client, AutoCompleter, Suggestion, IndexDefinition, TextField, TagField) |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 7 | |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 8 | WEBSITE_ITEM_INDEX = 'website_items_index' |
| 9 | WEBSITE_ITEM_KEY_PREFIX = 'website_item:' |
| 10 | WEBSITE_ITEM_NAME_AUTOCOMPLETE = 'website_items_name_dict' |
Hussain Nagaria | f342155 | 2021-04-26 11:15:20 +0530 | [diff] [blame] | 11 | WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict' |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 12 | |
| 13 | ALLOWED_INDEXABLE_FIELDS_SET = { |
marination | 2fec068 | 2021-07-13 23:46:24 +0530 | [diff] [blame] | 14 | 'web_item_name', |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 15 | 'item_code', |
| 16 | 'item_name', |
| 17 | 'item_group', |
| 18 | 'brand', |
| 19 | 'description', |
| 20 | 'web_long_description' |
| 21 | } |
| 22 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 23 | def 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 |
| 34 | def 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 | |
| 43 | def make_key(key): |
| 44 | return "{0}|{1}".format(frappe.conf.db_name, key).encode('utf-8') |
| 45 | |
| 46 | @if_redisearch_loaded |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 47 | def create_website_items_index(): |
| 48 | '''Creates Index Definition''' |
| 49 | # CREATE index |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 50 | client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache()) |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 51 | |
| 52 | # DROP if already exists |
| 53 | try: |
| 54 | client.drop_index() |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 55 | except Exception: |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 56 | pass |
| 57 | |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 58 | idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)]) |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 59 | |
| 60 | # Based on e-commerce settings |
| 61 | idx_fields = frappe.db.get_single_value( |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 62 | 'E Commerce Settings', |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 63 | 'search_index_fields' |
marination | 24ba06c | 2021-06-08 14:47:11 +0530 | [diff] [blame] | 64 | ) |
| 65 | idx_fields = idx_fields.split(',') if idx_fields else [] |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 66 | |
| 67 | if 'web_item_name' in idx_fields: |
| 68 | idx_fields.remove('web_item_name') |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 69 | |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 70 | idx_fields = list(map(to_search_field, idx_fields)) |
| 71 | |
| 72 | client.create_index( |
| 73 | [TextField("web_item_name", sortable=True)] + idx_fields, |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 74 | definition=idx_def, |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 75 | ) |
| 76 | |
| 77 | reindex_all_web_items() |
| 78 | define_autocomplete_dictionary() |
| 79 | |
| 80 | def to_search_field(field): |
| 81 | if field == "tags": |
| 82 | return TagField("tags", separator=",") |
| 83 | |
| 84 | return TextField(field) |
| 85 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 86 | @if_redisearch_loaded |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 87 | def insert_item_to_index(website_item_doc): |
| 88 | # Insert item to index |
| 89 | key = get_cache_key(website_item_doc.name) |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 90 | cache = frappe.cache() |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 91 | web_item = create_web_item_map(website_item_doc) |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 92 | |
| 93 | for k, v in web_item.items(): |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 94 | super(RedisWrapper, cache).hset(make_key(key), k, v) |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 95 | |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 96 | insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name) |
| 97 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 98 | @if_redisearch_loaded |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 99 | def insert_to_name_ac(web_name, doc_name): |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 100 | ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache()) |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 101 | ac.add_suggestions(Suggestion(web_name, payload=doc_name)) |
| 102 | |
| 103 | def create_web_item_map(website_item_doc): |
| 104 | fields_to_index = get_fields_indexed() |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 105 | 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 Nagaria | bd0d0ad | 2021-05-26 20:26:34 +0530 | [diff] [blame] | 111 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 112 | @if_redisearch_loaded |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 113 | def update_index_for_item(website_item_doc): |
| 114 | # Reinsert to Cache |
| 115 | insert_item_to_index(website_item_doc) |
| 116 | define_autocomplete_dictionary() |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 117 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 118 | @if_redisearch_loaded |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 119 | def delete_item_from_index(website_item_doc): |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 120 | cache = frappe.cache() |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 121 | key = get_cache_key(website_item_doc.name) |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 122 | |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 123 | try: |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 124 | cache.delete(key) |
marination | ea30ce4 | 2021-06-02 13:24:06 +0530 | [diff] [blame] | 125 | except Exception: |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 126 | return False |
| 127 | |
Hussain Nagaria | 34f3a66 | 2021-05-05 13:47:43 +0530 | [diff] [blame] | 128 | delete_from_ac_dict(website_item_doc) |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 129 | return True |
| 130 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 131 | @if_redisearch_loaded |
Hussain Nagaria | 34f3a66 | 2021-05-05 13:47:43 +0530 | [diff] [blame] | 132 | def delete_from_ac_dict(website_item_doc): |
| 133 | '''Removes this items's name from autocomplete dictionary''' |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 134 | cache = frappe.cache() |
| 135 | name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache) |
Hussain Nagaria | 34f3a66 | 2021-05-05 13:47:43 +0530 | [diff] [blame] | 136 | name_ac.delete(website_item_doc.web_item_name) |
| 137 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 138 | @if_redisearch_loaded |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 139 | def define_autocomplete_dictionary(): |
Hussain Nagaria | f342155 | 2021-04-26 11:15:20 +0530 | [diff] [blame] | 140 | """Creates an autocomplete search dictionary for `name`. |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 141 | Also creats autocomplete dictionary for `categories` if |
| 142 | checked in E Commerce Settings""" |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 143 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 144 | 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 Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 147 | |
Hussain Nagaria | f342155 | 2021-04-26 11:15:20 +0530 | [diff] [blame] | 148 | ac_categories = frappe.db.get_single_value( |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 149 | 'E Commerce Settings', |
Hussain Nagaria | f342155 | 2021-04-26 11:15:20 +0530 | [diff] [blame] | 150 | 'show_categories_in_search_autocomplete' |
| 151 | ) |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 152 | |
Hussain Nagaria | f342155 | 2021-04-26 11:15:20 +0530 | [diff] [blame] | 153 | # Delete both autocomplete dicts |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 154 | try: |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 155 | cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE)) |
| 156 | cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE)) |
marination | ea30ce4 | 2021-06-02 13:24:06 +0530 | [diff] [blame] | 157 | except Exception: |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 158 | return False |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 159 | |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 160 | items = frappe.get_all( |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 161 | 'Website Item', |
| 162 | fields=['web_item_name', 'item_group'], |
| 163 | filters={"published": 1} |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 164 | ) |
| 165 | |
| 166 | for item in items: |
Hussain Nagaria | f342155 | 2021-04-26 11:15:20 +0530 | [diff] [blame] | 167 | 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 Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 170 | |
| 171 | return True |
| 172 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 173 | @if_redisearch_loaded |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 174 | def reindex_all_web_items(): |
| 175 | items = frappe.get_all( |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 176 | 'Website Item', |
| 177 | fields=get_fields_indexed(), |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 178 | filters={"published": True} |
| 179 | ) |
| 180 | |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 181 | cache = frappe.cache() |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 182 | for item in items: |
| 183 | web_item = create_web_item_map(item) |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 184 | key = make_key(get_cache_key(item.name)) |
| 185 | |
| 186 | for k, v in web_item.items(): |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 187 | super(RedisWrapper, cache).hset(key, k, v) |
Hussain Nagaria | ffc8616 | 2021-04-29 20:47:32 +0530 | [diff] [blame] | 188 | |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 189 | def get_cache_key(name): |
| 190 | name = frappe.scrub(name) |
| 191 | return f"{WEBSITE_ITEM_KEY_PREFIX}{name}" |
| 192 | |
| 193 | def get_fields_indexed(): |
| 194 | fields_to_index = frappe.db.get_single_value( |
marination | b0d7e32 | 2021-06-01 12:44:49 +0530 | [diff] [blame] | 195 | 'E Commerce Settings', |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 196 | 'search_index_fields' |
marination | 24ba06c | 2021-06-08 14:47:11 +0530 | [diff] [blame] | 197 | ) |
| 198 | fields_to_index = fields_to_index.split(',') if fields_to_index else [] |
Hussain Nagaria | 8e55c95 | 2021-04-26 07:01:06 +0530 | [diff] [blame] | 199 | |
| 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 |
| 207 | define_autocomplete_dictionary() |
| 208 | create_website_items_index() |