Coverage for ckanext/udc/search/params.py: 11%
142 statements
« prev ^ index » next coverage.py v7.7.1, created at 2026-06-23 19:31 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2026-06-23 19:31 +0000
1from __future__ import annotations
3from collections import OrderedDict
4from datetime import datetime
5from typing import Any, Iterable, Optional
7import ckan.plugins as plugins
8from ckan.common import request
10from ckanext.udc.solr.config import get_current_lang
13UDC_PARAM_PREFIX = "ext_udc_"
14CKAN_FTS_FIELDS = [
15 "title",
16 "notes",
17 "url",
18 "version",
19 "author",
20 "author_email",
21 "maintainer",
22 "maintainer_email",
23]
24CORE_STRING_FACETS = ["organization", "groups", "license_id", "res_format"]
27def _solr_field_for(
28 param_kind: str,
29 ui_field: str,
30 lang: str,
31 text_fields: set[str],
32) -> Optional[str]:
33 if ui_field in text_fields:
34 if param_kind == "fts":
35 return f"{ui_field}_{lang}_txt"
36 if param_kind == "exact":
37 return f"{ui_field}_{lang}_f"
38 if param_kind in ("min", "max"):
39 return f"extras_{ui_field}"
41 if ui_field == "tags":
42 if param_kind == "fts":
43 return f"tags_{lang}_txt"
44 if param_kind == "exact":
45 return f"tags_{lang}_f"
47 if ui_field in CKAN_FTS_FIELDS:
48 if param_kind == "fts":
49 return f"{ui_field}_{lang}_txt"
50 return None
52 if ui_field in CORE_STRING_FACETS:
53 if param_kind == "exact":
54 return ui_field
55 return None
57 if ui_field == "portal_type" and param_kind == "exact":
58 return "extras_portal_type"
60 if param_kind in ("min", "max", "exact", "fts"):
61 return f"extras_{ui_field}"
63 return None
66def _decode_udc_param(param: str) -> tuple[Optional[str], Optional[str]]:
67 if not param.startswith(UDC_PARAM_PREFIX):
68 return None, None
70 suffix = param[len(UDC_PARAM_PREFIX):]
71 for kind in ("filter_logic", "exact", "fts", "min", "max"):
72 prefix = f"{kind}_"
73 if suffix.startswith(prefix):
74 return kind, suffix[len(prefix):]
76 return None, None
79def _request_items() -> Iterable[tuple[str, str]]:
80 try:
81 return request.args.items(multi=True)
82 except RuntimeError:
83 return []
86def get_search_details(
87 params: Optional[Iterable[tuple[str, str]]] = None,
88) -> dict[str, Any]:
89 fq = ""
90 fields: list[tuple[str, str]] = []
91 fields_grouped: dict[str, Any] = {}
92 filter_logics: dict[str, str] = {}
93 include_undefined: set[str] = set()
95 udc = plugins.get_plugin("udc")
96 text_fields = set(udc.text_fields or [])
97 date_fields = set(udc.date_fields or [])
98 lang = get_current_lang()
100 for param, value in params if params is not None else _request_items():
101 if not value:
102 continue
104 kind, ui_name = _decode_udc_param(param)
105 if not kind or not ui_name:
106 continue
108 if kind == "filter_logic":
109 if value.lower() == "and":
110 filter_logics[ui_name] = "AND"
111 elif value in ("date", "number"):
112 solr_key = _solr_field_for("min", ui_name, lang, text_fields)
113 if solr_key:
114 include_undefined.add(solr_key)
115 continue
117 solr_kind = "exact" if kind == "exact" else kind
118 solr_key = _solr_field_for(solr_kind, ui_name, lang, text_fields)
119 if not solr_key:
120 continue
122 if kind in ("fts", "exact"):
123 fields.append((param, value))
124 group = fields_grouped.setdefault(
125 solr_key,
126 {"ui": ui_name, "fts": kind == "fts", "values": [], "params": []},
127 )
128 group["values"].append(value)
129 group["params"].append(param)
130 continue
132 group = fields_grouped.setdefault(solr_key, {"ui": ui_name})
133 group[kind] = value
134 group[f"{kind}_param"] = param
136 for solr_key, opts in fields_grouped.items():
137 if "values" in opts:
138 vals = opts["values"]
139 ui_name = opts.get("ui", solr_key)
140 logic_op = filter_logics.get(ui_name, "OR")
141 if len(vals) > 1:
142 joined = f" {logic_op} ".join([f'"{v}"' for v in vals])
143 fq += f" {solr_key}:({joined})"
144 else:
145 fq += f' {solr_key}:"{vals[0]}"'
146 continue
148 min_value = opts.get("min")
149 max_value = opts.get("max")
150 ui_name = opts.get("ui")
151 if ui_name and solr_key.startswith("extras_") and ui_name in date_fields:
152 try:
153 if min_value:
154 min_value = datetime.strptime(
155 min_value, "%Y-%m-%d"
156 ).strftime("%Y-%m-%dT%H:%M:%SZ")
157 if max_value:
158 max_date = datetime.strptime(max_value, "%Y-%m-%d")
159 max_value = max_date.replace(
160 hour=23, minute=59, second=59
161 ).strftime("%Y-%m-%dT%H:%M:%SZ")
162 except ValueError:
163 continue
165 if min_value and max_value:
166 range_query = f" {solr_key}:[{min_value} TO {max_value}]"
167 elif min_value:
168 range_query = f" {solr_key}:[{min_value} TO *]"
169 elif max_value:
170 range_query = f" {solr_key}:[* TO {max_value}]"
171 else:
172 range_query = ""
174 if range_query:
175 if solr_key in include_undefined:
176 range_query = f"({range_query} OR (*:* -{solr_key}:[* TO *]))"
177 fq += range_query
179 return {
180 "fields": fields,
181 "fields_grouped": fields_grouped,
182 "filter_logics": filter_logics,
183 "fq": fq,
184 }
187def facet_alias_map(facet_keys: Iterable[str], lang: Optional[str] = None):
188 udc = plugins.get_plugin("udc")
189 text_fields = set(udc.text_fields or [])
190 lang = lang or get_current_lang()
192 alias_to_solr: OrderedDict[str, str] = OrderedDict()
193 for key in facet_keys:
194 if key == "tags":
195 alias_to_solr[key] = f"tags_{lang}_f"
196 elif key == "portal_type":
197 alias_to_solr[key] = "extras_portal_type"
198 elif key == "version_dataset":
199 alias_to_solr[key] = "version_dataset_url"
200 elif key == "dataset_versions":
201 alias_to_solr[key] = "dataset_versions_url"
202 elif key.startswith("extras_"):
203 alias_to_solr[key] = key
204 elif key in text_fields:
205 alias_to_solr[key] = f"{key}_{lang}_f"
206 else:
207 alias_to_solr[key] = key
209 solr_fields = list(dict.fromkeys(alias_to_solr.values()))
210 return solr_fields, alias_to_solr