diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2019-01-31 17:06:29 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2019-01-31 17:06:29 +0100 |
commit | 61dee247a1614517c8fb73eb7ada2e33019134b9 (patch) | |
tree | 013b56c339effd0a0540f5cee907986e30fd897a /ishtar_common | |
parent | bf3794dcccee7a46ab8bf7177ab119ffb437f6dd (diff) | |
download | Ishtar-61dee247a1614517c8fb73eb7ada2e33019134b9.tar.bz2 Ishtar-61dee247a1614517c8fb73eb7ada2e33019134b9.zip |
Search: manage dynamic query parameters (ex: divisions details)
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/forms.py | 18 | ||||
-rw-r--r-- | ishtar_common/models.py | 159 | ||||
-rw-r--r-- | ishtar_common/views_item.py | 113 |
3 files changed, 199 insertions, 91 deletions
diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index 2a072aade..93c6cd8ed 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -569,17 +569,25 @@ class IshtarForm(forms.Form, BSForm): class TableSelect(IshtarForm): def __init__(self, *args, **kwargs): super(TableSelect, self).__init__(*args, **kwargs) - ALT_NAMES = {} - if hasattr(self, '_model') and hasattr(self._model, "ALT_NAMES"): - ALT_NAMES = self._model.ALT_NAMES + alt_names = {} + if hasattr(self, '_model'): + if hasattr(self._model, "get_alt_names"): + alt_names = self._model.get_alt_names() + + for k_dyn in self._model.DYNAMIC_REQUESTS: + dyn = self._model.DYNAMIC_REQUESTS[k_dyn] + fields = dyn.get_form_fields() + for k in fields: + self.fields[k] = fields[k] + for k in self.fields: self.fields[k].required = False # no field is required for search cls = 'form-control' if k == 'search_vector': cls += " search-vector" self.fields[k].widget.attrs['class'] = cls - if k in ALT_NAMES: - self.fields[k].alt_name = ALT_NAMES[k][0] + if k in alt_names: + self.fields[k].alt_name = alt_names[k].search_key else: self.fields[k].alt_name = k key = self.fields.keys()[0] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 12f7dc47b..7f5857037 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -33,9 +33,10 @@ import tempfile import time from cStringIO import StringIO from subprocess import Popen, PIPE - - from PIL import Image + +from django import forms +from django.apps import apps from django.conf import settings from django.contrib.auth.models import User, Group from django.contrib.contenttypes.fields import GenericForeignKey @@ -1240,9 +1241,62 @@ class Imported(models.Model): abstract = True +class SearchAltName(object): + def __init__(self, search_key, search_query, extra_query=None, + distinct_query=False): + self.search_key = search_key + self.search_query = search_query + self.extra_query = extra_query or {} + self.distinct_query = distinct_query + + +class DynamicRequest(object): + def __init__(self, label, app_name, model_name, form_key, search_key, + type_query, search_query): + self.label = label + self.form_key = form_key + self.search_key = search_key + self.app_name = app_name + self.model_name = model_name + self.type_query = type_query + self.search_query = search_query + + def get_all_types(self): + model = apps.get_app_config(self.app_name).get_model(self.model_name) + return model.objects.filter(available=True) + + def get_form_fields(self): + fields = {} + for item in self.get_all_types().all(): + fields[self.form_key + "-" + item.txt_idx] = forms.CharField( + label=unicode(self.label) + u" " + unicode(item), + required=False + ) + return fields + + def get_extra_query(self, slug): + return { + self.type_query: slug + } + + def get_alt_names(self): + alt_names = {} + for item in self.get_all_types().all(): + alt_names[self.form_key + "-" + item.txt_idx] = SearchAltName( + self.search_key + "-" + item.txt_idx, self.search_query, + self.get_extra_query(item.txt_idx), distinct_query=True + ) + return alt_names + + class FullSearch(models.Model): search_vector = SearchVectorField(_("Search vector"), blank=True, null=True, help_text=_("Auto filled at save")) + + EXTRA_REQUEST_KEYS = {} + DYNAMIC_REQUESTS = {} + ALT_NAMES = {} + BASE_SEARCH_VECTORS = [] PROPERTY_SEARCH_VECTORS = [] INT_SEARCH_VECTORS = [] @@ -1264,6 +1318,23 @@ class FullSearch(models.Model): if issubclass(rel_model, (GeneralType, HierarchicalType)): yield k + @classmethod + def get_alt_names(cls): + alt_names = cls.ALT_NAMES.copy() + for dr_k in cls.DYNAMIC_REQUESTS: + alt_names.update(cls.DYNAMIC_REQUESTS[dr_k].get_alt_names()) + return alt_names + + @classmethod + def get_query_parameters(cls): + query_parameters = {} + for v in cls.get_alt_names().values(): + for language_code, language_lbl in settings.LANGUAGES: + activate(language_code) + query_parameters[unicode(v.search_key)] = v + deactivate() + return query_parameters + def update_search_vector(self, save=True, exclude_parent=False): """ Update the search vector @@ -2692,7 +2763,6 @@ class Department(models.Model): return res - class Address(BaseHistorizedItem): address = models.TextField(_(u"Address"), null=True, blank=True) address_complement = models.TextField(_(u"Address complement"), null=True, @@ -2880,20 +2950,16 @@ class Organization(Address, Merge, OwnPerms, ValueGetter): # alternative names of fields for searches ALT_NAMES = { - 'name': ( + 'name': SearchAltName( pgettext_lazy("key for text search", u"name"), 'name__iexact' ), - 'organization_type': ( + 'organization_type': SearchAltName( pgettext_lazy("key for text search", u"type"), 'organization_type__label__iexact' ), } - for v in ALT_NAMES.values(): - for language_code, language_lbl in settings.LANGUAGES: - activate(language_code) - EXTRA_REQUEST_KEYS[unicode(v[0])] = v[1] - deactivate() + objects = OrganizationManager() # fields @@ -3029,36 +3095,32 @@ class Person(Address, Merge, OwnPerms, ValueGetter): # alternative names of fields for searches ALT_NAMES = { - 'name': ( + 'name': SearchAltName( pgettext_lazy("key for text search", u"name"), 'name__iexact' ), - 'surname': ( + 'surname': SearchAltName( pgettext_lazy("key for text search", u"surname"), 'surname__iexact' ), - 'email': ( + 'email': SearchAltName( pgettext_lazy("key for text search", u"email"), 'email__iexact' ), - 'person_types': ( + 'person_types': SearchAltName( pgettext_lazy("key for text search", u"type"), 'person_types__label__iexact' ), - 'attached_to': ( + 'attached_to': SearchAltName( pgettext_lazy("key for text search", u"organization"), 'attached_to__cached_label__iexact' ), - 'ishtaruser__isnull': ( + 'ishtaruser__isnull': SearchAltName( pgettext_lazy("key for text search", u"has-account"), 'ishtaruser__isnull' ), } - for v in ALT_NAMES.values(): - for language_code, language_lbl in settings.LANGUAGES: - activate(language_code) - EXTRA_REQUEST_KEYS[unicode(v[0])] = v[1] - deactivate() + objects = PersonManager() # fields @@ -3433,36 +3495,31 @@ class IshtarUser(FullSearch): # alternative names of fields for searches ALT_NAMES = { - 'username': ( + 'username': SearchAltName( pgettext_lazy("key for text search", u"username"), 'user_ptr__username__iexact' ), - 'name': ( + 'name': SearchAltName( pgettext_lazy("key for text search", u"name"), 'person__name__iexact' ), - 'surname': ( + 'surname': SearchAltName( pgettext_lazy("key for text search", u"surname"), 'person__surname__iexact' ), - 'email': ( + 'email': SearchAltName( pgettext_lazy("key for text search", u"email"), 'person__email__iexact' ), - 'person_types': ( + 'person_types': SearchAltName( pgettext_lazy("key for text search", u"type"), 'person__person_types__label__iexact' ), - 'attached_to': ( + 'attached_to': SearchAltName( pgettext_lazy("key for text search", u"organization"), 'person__attached_to__cached_label__iexact' ), } - for v in ALT_NAMES.values(): - for language_code, language_lbl in settings.LANGUAGES: - activate(language_code) - EXTRA_REQUEST_KEYS[unicode(v[0])] = v[1] - deactivate() # fields user_ptr = models.OneToOneField(User, primary_key=True, @@ -3807,76 +3864,72 @@ class Document(BaseHistorizedItem, OwnPerms, ImageModel): # alternative names of fields for searches ALT_NAMES = { - 'authors': ( + 'authors': SearchAltName( pgettext_lazy("key for text search", u"author"), 'authors__cached_label__iexact' ), - 'title': ( + 'title': SearchAltName( pgettext_lazy("key for text search", u"title"), 'title__iexact' ), - 'source_type': ( + 'source_type': SearchAltName( pgettext_lazy("key for text search", u"type"), 'source_type__label__iexact' ), - 'reference': ( + 'reference': SearchAltName( pgettext_lazy("key for text search", u"reference"), 'reference__iexact' ), - 'internal_reference': ( + 'internal_reference': SearchAltName( pgettext_lazy("key for text search", u"internal-reference"), 'internal_reference__iexact' ), - 'description': ( + 'description': SearchAltName( pgettext_lazy("key for text search", u"description"), 'description__iexact' ), - 'comment': ( + 'comment': SearchAltName( pgettext_lazy("key for text search", u"comment"), 'comment__iexact' ), - 'additional_information': ( + 'additional_information': SearchAltName( pgettext_lazy("key for text search", u"additional-information"), 'additional_information__iexact' ), - 'duplicate': ( + 'duplicate': SearchAltName( pgettext_lazy("key for text search", u"has-duplicate"), 'duplicate' ), - 'operation': ( + 'operation': SearchAltName( pgettext_lazy("key for text search", u"operation"), 'operations__cached_label__iexact' ), - 'context_record': ( + 'context_record': SearchAltName( pgettext_lazy("key for text search", u"context-record"), 'context_records__cached_label__iexact' ), - 'find': ( + 'find': SearchAltName( pgettext_lazy("key for text search", u"find"), 'finds__cached_label__iexact' ), - 'find__denomination': ( + 'find__denomination': SearchAltName( pgettext_lazy("key for text search", u"find-denomination"), 'finds__denomination__iexact' ), - 'file': ( + 'file': SearchAltName( pgettext_lazy("key for text search", u"file"), 'files__cached_label__iexact' ), - 'site': ( + 'site': SearchAltName( pgettext_lazy("key for text search", u"site"), 'sites__cached_label__iexact' ), - 'warehouse': ( + 'warehouse': SearchAltName( pgettext_lazy("key for text search", u"warehouse"), 'warehouses__name__iexact' ), } - for v in ALT_NAMES.values(): - for language_code, language_lbl in settings.LANGUAGES: - activate(language_code) - EXTRA_REQUEST_KEYS[unicode(v[0])] = v[1] - deactivate() + objects = ExternalIdManager() RELATED_MODELS_ALT = [ diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py index 8af79c6fa..437bbd651 100644 --- a/ishtar_common/views_item.py +++ b/ishtar_common/views_item.py @@ -32,7 +32,7 @@ from weasyprint.fonts import FontConfiguration from ishtar_common.utils import check_model_access_control, CSV_OPTIONS, \ get_all_field_names from ishtar_common.models import HistoryError, get_current_profile, \ - PRIVATE_FIELDS, GeneralType + PRIVATE_FIELDS, GeneralType, SearchAltName from menus import Menu import models @@ -381,21 +381,23 @@ FORBIDDEN_CHAR = [u":"] RESERVED_CHAR = [u"|", u"&"] -def _parse_query_string(string, request_keys, current_dct, exc_dct): +def _parse_query_string(string, query_parameters, current_dct, exc_dct, + extra_distinct_q): string = string.strip().lower() if u"=" in string: splited = string.split(u"=") if len(splited) == 2: - term, query = splited - excluded = term.startswith(u"-") + base_term, query = splited + excluded = base_term.startswith(u"-") if excluded: - term = term[1:] - if term in request_keys: + base_term = base_term[1:] + if base_term in query_parameters: dct = current_dct - term = request_keys[term] + term = query_parameters[base_term].search_query # callable request key for complex queries if callable(term): + # !! distinct_queries not managed is_true = is_true_string(query) if excluded: is_true = not is_true @@ -411,12 +413,26 @@ def _parse_query_string(string, request_keys, current_dct, exc_dct): if cextra: dct['extras'].append(cextra) else: - if excluded: + if query_parameters[base_term].distinct_query: + extra_distinct_q.append({}) + dct = extra_distinct_q[-1] + if not query_parameters[base_term].distinct_query and \ + excluded: dct = exc_dct + if query_parameters[base_term].extra_query: + dct.update(query_parameters[base_term].extra_query) if term in dct: dct[term] += u";" + query else: dct[term] = query + if query_parameters[base_term].distinct_query: + for k in dct: # clean " + dct[k] = dct[k].replace('"', '') + # distinct query wait for a query + if excluded: + extra_distinct_q[-1] = ~Q(**dct) + else: + extra_distinct_q[-1] = Q(**dct) return u"" for reserved_char in FORBIDDEN_CHAR: string = string.replace(reserved_char, u"") @@ -436,21 +452,24 @@ def _parse_query_string(string, request_keys, current_dct, exc_dct): return string -def _parse_parentheses_groups(groups, request_keys, current_dct=None, - exc_dct=None): +def _parse_parentheses_groups(groups, query_parameters, current_dct=None, + exc_dct=None, extra_distinct_q=None): """ Transform parentheses groups to query :param groups: groups to transform (list) - :param request_keys: request keys for facet search + :param query_parameters: query keys for facet search :param current_dct: query dict :param exc_dct: exclude query dict + :param exc_dct: exclude query dict :return: query string, query dict, excluded query dict """ if not current_dct: current_dct = {} if not exc_dct: exc_dct = {} + if not extra_distinct_q: + extra_distinct_q = [] if type(groups) is not list: string = groups.strip() if string.startswith(u'"') and string.endswith(u'"') and \ @@ -479,17 +498,19 @@ def _parse_parentheses_groups(groups, request_keys, current_dct=None, string_groups = [gp.replace(SEP, u" ") for gp in string.split(u" ")] if len(string_groups) == 1: return _parse_query_string( - string_groups[0], request_keys, current_dct, exc_dct), \ - current_dct, exc_dct - return _parse_parentheses_groups(string_groups, - request_keys, current_dct, exc_dct) + string_groups[0], query_parameters, current_dct, exc_dct, + extra_distinct_q + ), current_dct, exc_dct, extra_distinct_q + return _parse_parentheses_groups( + string_groups, query_parameters, current_dct, exc_dct, + extra_distinct_q) if not groups: # empty list - return "", current_dct, exc_dct + return "", current_dct, exc_dct, extra_distinct_q query = u"(" previous_sep, has_item = None, False for item in groups: - q, current_dct, exc_dct = _parse_parentheses_groups( - item, request_keys, current_dct, exc_dct) + q, current_dct, exc_dct, extra_distinct_q = _parse_parentheses_groups( + item, query_parameters, current_dct, exc_dct, extra_distinct_q) q = q.strip() if not q: continue @@ -509,18 +530,20 @@ def _parse_parentheses_groups(groups, request_keys, current_dct=None, query += u")" if query == u"()": query = u"" - return unidecode(query), current_dct, exc_dct + return unidecode(query), current_dct, exc_dct, extra_distinct_q -def _search_manage_search_vector(model, dct, exc_dct, request_keys): +def _search_manage_search_vector(model, dct, exc_dct, distinct_queries, + query_parameters): if 'search_vector' not in dct: - return dct, exc_dct + return dct, exc_dct, distinct_queries search_vector = dct['search_vector'] parentheses_groups = _parse_parentheses(search_vector) - search_query, extra_dct, extra_exc_dct = _parse_parentheses_groups( - parentheses_groups, request_keys) + search_query, extra_dct, extra_exc_dct, extra_distinct_q = \ + _parse_parentheses_groups(parentheses_groups, query_parameters) dct.update(extra_dct) + distinct_queries += extra_distinct_q exc_dct.update(extra_exc_dct) if search_query: @@ -536,7 +559,7 @@ def _search_manage_search_vector(model, dct, exc_dct, request_keys): 'params': [settings.ISHTAR_SEARCH_LANGUAGE, search_query]} ) - return dct, exc_dct + return dct, exc_dct, distinct_queries def _manage_bool_fields(model, bool_fields, reversed_bool_fields, dct, or_reqs): @@ -1033,11 +1056,18 @@ def get_item(model, func_name, default_name, extra_request_keys=None, else: query_own = model.get_query_owns(q.all()[0]) + query_parameters = {} + + if hasattr(model, 'get_query_parameters'): + query_parameters = model.get_query_parameters() + # get defaults from model - if not extra_request_keys and hasattr(model, 'EXTRA_REQUEST_KEYS'): + if not extra_request_keys and query_parameters: my_extra_request_keys = copy(model.EXTRA_REQUEST_KEYS) + for key in query_parameters: + my_extra_request_keys[key] = query_parameters[key].search_query else: - my_extra_request_keys = copy(extra_request_keys or []) + my_extra_request_keys = copy(extra_request_keys or {}) if base_request is None and hasattr(model, 'BASE_REQUEST'): if callable(model.BASE_REQUEST): my_base_request = model.BASE_REQUEST(request) @@ -1139,6 +1169,7 @@ def get_item(model, func_name, default_name, extra_request_keys=None, excluded_dct = {} and_reqs, or_reqs = [], [] exc_and_reqs, exc_or_reqs = [], [] + distinct_queries = [] dct['extras'], dct['and_reqs'], dct['exc_and_reqs'] = [], [], [] if full == 'shortcut': @@ -1158,11 +1189,19 @@ def get_item(model, func_name, default_name, extra_request_keys=None, if not val: continue req_keys = request_keys[k] + target = dct + if k in query_parameters: + if query_parameters[k].distinct_query: + distinct_queries.append({}) + target = distinct_queries[-1] + if query_parameters[k].extra_query: + target.update(query_parameters[k].extra_query) + if callable(req_keys): # callable request key for complex queries not managed on GET continue elif type(req_keys) not in (list, tuple): - dct[req_keys] = val + target[req_keys] = val continue # multiple choice target reqs = Q(**{req_keys[0]: val}) @@ -1184,8 +1223,14 @@ def get_item(model, func_name, default_name, extra_request_keys=None, else: request.session[func_name] = dct - dct, excluded_dct = _search_manage_search_vector( - model, dct, excluded_dct, request_keys) + for k in request_keys: + if k not in query_parameters: + query_parameters[k] = SearchAltName(k, request_keys[k]) + + dct, excluded_dct, distinct_queries = _search_manage_search_vector( + model, dct, excluded_dct, distinct_queries, query_parameters, + ) + search_vector = "" if 'search_vector' in dct: search_vector = dct.pop('search_vector') @@ -1254,9 +1299,12 @@ def get_item(model, func_name, default_name, extra_request_keys=None, if current: dct = {upper_key: current} query &= Q(**dct) - # print(query) items = model.objects.filter(query) + for d_q in distinct_queries: + print(d_q) + items = items.filter(d_q) + if base_query: items = items.filter(base_query) if exc_query: @@ -1422,9 +1470,8 @@ def get_item(model, func_name, default_name, extra_request_keys=None, keys = [keys] my_vals = [] for k in keys: - if hasattr(model, 'EXTRA_REQUEST_KEYS') \ - and k in model.EXTRA_REQUEST_KEYS: - k = model.EXTRA_REQUEST_KEYS[k] + if k in my_extra_request_keys: + k = my_extra_request_keys[k] if type(k) in (list, tuple): k = k[0] for filtr in ('__icontains', '__contains', '__iexact', |