summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
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
commit61dee247a1614517c8fb73eb7ada2e33019134b9 (patch)
tree013b56c339effd0a0540f5cee907986e30fd897a /ishtar_common
parentbf3794dcccee7a46ab8bf7177ab119ffb437f6dd (diff)
downloadIshtar-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.py18
-rw-r--r--ishtar_common/models.py159
-rw-r--r--ishtar_common/views_item.py113
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',