Coverage for ckanext/udc/plugin.py: 79%
291 statements
« prev ^ index » next coverage.py v7.7.1, created at 2026-03-30 22:15 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2026-03-30 22:15 +0000
1from __future__ import annotations
3import sys
4import json
5import traceback
6import chalk
7from collections import OrderedDict
8from typing import (
9 Any,
10 Callable,
11 Collection,
12 KeysView,
13 Optional,
14 Union,
15 cast,
16 Iterable,
17 List,
18)
19from functools import partial
21from ckanext.udc.search.logic.actions import filter_facets_get
22from ckan.types import Schema, Context, CKANApp, Response, SignalMapping
23import ckan
24import ckan.plugins as plugins
25import ckan.logic as logic
26import ckan.model as model
27import ckan.plugins.toolkit as tk
28import ckan.lib.base as base
29from ckan.plugins.toolkit import chained_action, side_effect_free
30import ckan.lib.helpers as h
31from ckan.lib.helpers import Page
32from ckan.common import asbool, current_user, CKANConfig, request, g, config, _
33from ckan.lib.search import SearchQueryError, SearchError
34from ckan.lib.plugins import DefaultTranslation
36from flask import Blueprint
38import logging
39from ckanext.udc.cli import udc as cli_udc
40from ckanext.udc.validator import udc_config_validator
41from ckanext.udc.helpers import (
42 config_option_update,
43 get_default_facet_titles,
44 process_facets_fields,
45 humanize_entity_type,
46 get_maturity_percentages,
47 package_update,
48 package_delete,
49 get_system_info,
50 udc_json_attr,
51 render_markdown,
52)
53from ckanext.udc.solr.config import pick_locale, pick_locale_with_fallback, get_udc_langs, get_current_lang
54from ckanext.udc.graph.sparql_client import SparqlClient
55from ckanext.udc.graph.preload import preload_ontologies
56from ckanext.udc.graph.logic import get_catalogue_graph
57from babel import Locale
59from ckanext.udc.licenses.logic.action import (
60 license_create,
61 license_delete,
62 licenses_get,
63 license_update,
64 init_licenses,
65 test_long_task,
66)
67from ckanext.udc.licenses.utils import license_options_details
68from ckanext.udc.file_format.logic import (
69 file_format_create,
70 file_format_delete,
71 file_formats_get,
72)
73from ckanext.udc.desc.actions import (
74 summary_generate,
75 update_summary,
76 default_ai_summary_config,
77)
78from ckanext.udc.desc.utils import init_plugin as init_udc_desc
79from ckanext.udc.error_handler import override_error_handler
80from ckanext.udc.system.actions import reload_supervisord, get_system_stats
81from ckanext.udc.version.actions import udc_version_meta
82from ckanext.udc.solr.solr import update_solr_maturity_model_fields
83from ckanext.udc.solr.index import before_dataset_index as _before_dataset_index
85from ckanext.udc.search.logic.actions import filter_facets_get
86from ckanext.udc.user.actions import (
87 deleted_users_list,
88 purge_deleted_users,
89 udc_user_list,
90 udc_user_reset_password,
91 udc_user_delete,
92)
93from ckanext.udc.organization.actions import (
94 udc_organization_list,
95 udc_deleted_organization_list,
96 udc_organization_delete,
97 udc_purge_deleted_organizations,
98 udc_organization_packages_list,
99 udc_organization_packages_ids,
100 udc_organization_packages_delete,
101)
102from ckanext.udc.organization import auth as organization_auth
103from ckanext.udc.user import auth as user_auth
104from .i18n import (
105 udc_lang_object,
106 udc_json_dump,
107 udc_json_load,
108 udc_core_translated_to_extras,
109 udc_set_core_from_translated,
110 udc_lang_string_list,
111 udc_set_core_tags_from_translated,
112 udc_fill_tags_translated_from_core,
113 udc_seed_translated_from_core,
114 udc_fill_translated_from_core_on_show,
115)
118"""
119See https://docs.ckan.org/en/latest/theming/templates.html
120See https://docs.ckan.org/en/latest/extensions/adding-custom-fields.html
121See https://docs.ckan.org/en/2.10/extensions/remote-config-update.html
122See https://docs.ckan.org/en/2.10/extensions/custom-config-settings.html?highlight=config%20declaration
123See https://docs.ckan.org/en/2.10/theming/webassets.html
124"""
125log = logging.getLogger(__name__)
127# Add UDC CLI
128# We can then `ckan -c /etc/ckan/default/ckan.ini udc move-to-catalogues` to run the migration script
129if hasattr(ckan, "cli") and hasattr(ckan.cli, "cli"):
130 ckan.cli.cli.ckan.add_command(cli_udc.udc)
133@tk.blanket.blueprints
134class UdcPlugin(plugins.SingletonPlugin, tk.DefaultDatasetForm, DefaultTranslation):
135 plugins.implements(plugins.IConfigurer)
136 plugins.implements(plugins.IConfigurable)
137 plugins.implements(plugins.IDatasetForm)
138 plugins.implements(plugins.ITemplateHelpers)
139 plugins.implements(plugins.IActions)
140 plugins.implements(plugins.IAuthFunctions)
141 plugins.implements(plugins.IFacets)
142 plugins.implements(plugins.IPackageController)
143 plugins.implements(plugins.IMiddleware)
144 plugins.implements(plugins.IBlueprint)
145 plugins.implements(plugins.IValidators)
146 plugins.implements(plugins.ITranslation)
148 disable_graphdb = False
149 maturity_model = []
150 mappings = {}
151 preload_ontologies = {}
152 all_fields: List[str] = [] # This does not contain core CKAN fields
153 facet_titles = {}
154 facet_titles_raw = {} # multilingual titles, not picked to current locale
155 text_fields: List[str] = []
156 date_fields: List[str] = []
157 multiple_select_fields: List[str] = []
158 dropdown_options: dict[str, dict[str, str]] = {}
160 def update_config(self, config_):
161 tk.add_template_directory(config_, "templates")
162 tk.add_public_directory(config_, "public")
163 tk.add_resource("assets", "udc")
165 def _cli_configure(self):
166 """Partial load for CLI"""
167 self.disable_graphdb = True
168 existing_config = ckan.model.system_info.get_system_info("ckanext.udc.config")
169 if existing_config:
170 try:
171 # Call our plugin to update the config
172 self.reload_config(json.loads(existing_config))
173 except:
174 log.error
176 # Load custom licenses
177 init_licenses()
179 def configure(self, config: CKANConfig):
180 log.info(sys.argv)
181 if not (
182 "run" in sys.argv
183 or "uwsgi" in sys.argv
184 or ("jobs" in sys.argv and "worker" in sys.argv)
185 or ("search-index" in sys.argv)
186 or ("translation" in sys.argv)
187 or ("asset" in sys.argv)
188 ):
189 log.info("Skipping UDC Plugin Configuration")
190 # Do not load the plugin if we are running the CLI
191 self._cli_configure()
192 return
193 existing_config = ckan.model.system_info.get_system_info("ckanext.udc.config")
194 # print(existing_config)
196 # Load sparql client
197 endpoint = tk.config.get("udc.sparql.endpoint")
198 username = tk.config.get("udc.sparql.username") or None
199 password = tk.config.get("udc.sparql.password") or None
201 if endpoint is None:
202 self.disable_graphdb = True
203 log.info("No GraphDB Endpoint is provided.")
205 else:
206 self.sparql_client = SparqlClient(
207 endpoint, username=username, password=password
208 )
209 if self.sparql_client.test_connecetion():
210 log.info("GraphDB connected: " + endpoint)
211 else:
212 log.error("UDC cannot connect to the GraphDB")
213 self.disable_graphdb = True
215 # Load config
216 if existing_config:
217 try:
218 # Call our plugin to update the config
219 self.reload_config(json.loads(existing_config))
220 except:
221 log.error
223 # Load custom licenses
224 init_licenses()
226 # Init chatgpt summary plugin
227 init_udc_desc()
229 log.info("UDC Plugin Loaded!")
231 def reload_config(self, config: list):
232 try:
233 # log.info("tring to load udc config:")
234 # log.info(config)
235 all_fields = []
236 self.facet_titles.clear()
237 self.facet_titles_raw.clear()
238 self.text_fields.clear()
239 self.date_fields.clear()
240 self.multiple_select_fields.clear()
241 self.dropdown_options.clear()
242 for level in config["maturity_model"]:
243 for field in level["fields"]:
244 field_name = field.get("name")
245 if field.get("ckanField") == "portal_type":
246 field_name = "portal_type"
248 if field_name and field.get("name"):
249 all_fields.append(field_name)
250 type = field.get("type")
251 if field_name and (
252 type == ""
253 or type is None
254 or type == "text"
255 or type == "single_select"
256 or type == "multiple_select"
257 or type == "number"
258 or type == "date"
259 ):
260 self.facet_titles[field_name] = tk._(pick_locale(field["label"], 'en'))
261 self.facet_titles_raw[field_name] = field["label"]
262 if field_name and (type == "text" or type is None):
263 self.text_fields.append(field_name)
264 if field.get("type") == "date":
265 self.date_fields.append(field_name)
266 if field.get("type") == "multiple_select":
267 self.multiple_select_fields.append(field_name)
269 # Preload ontologies
270 if not self.disable_graphdb:
271 endpoint = tk.config.get("udc.sparql.endpoint")
272 username = tk.config.get("udc.sparql.username") or None
273 password = tk.config.get("udc.sparql.password") or None
274 # This will preload ontologies and
275 # populate options to the fields that uses 'optionsFromQuery'
276 preload_ontologies(
277 config, endpoint, username, password, self.sparql_client
278 )
280 # Store dropdown options
281 for level in config["maturity_model"]:
282 for field in level["fields"]:
283 if (
284 field.get("type") == "multiple_select"
285 or field.get("type") == "single_select"
286 ):
287 field_name = field.get("name")
288 if field.get("ckanField") == "portal_type":
289 field_name = "portal_type"
290 if not field_name:
291 continue
292 options = self.dropdown_options[field_name] = {}
293 for option in field["options"]:
294 options[option["value"]] = tk._(option["text"])
295 log.info(
296 f"Dropdown options for field {field_name} loaded: {len(options.keys())}"
297 )
299 # Update solr index
300 update_solr_maturity_model_fields(config["maturity_model"])
302 # Do not mutate the vars
303 self.all_fields.clear()
304 self.all_fields.extend(all_fields)
305 self.maturity_model.clear()
306 self.maturity_model.extend(config["maturity_model"])
307 self.mappings.clear()
308 self.mappings.update(config["mappings"])
309 self.preload_ontologies.clear()
310 self.preload_ontologies.update(config["preload_ontologies"])
312 except Exception as e:
313 log.error("UDC Plugin Error:")
314 traceback.print_exc()
316 def _modify_package_schema(self, schema: Schema) -> Schema:
317 """
318 Wire CUDC custom fields into CKAN:
319 - For type="text" custom fields: accept multilingual {lang: value} and store as JSON string in extras.
320 - For other types (date/number/select/etc.): keep original convert_to_extras for now.
321 """
322 # our custom fields
323 for field in self.all_fields:
324 if field in self.text_fields:
325 # Multilingual text: validate object -> dump JSON -> extras
326 schema.update(
327 {
328 field: [
329 tk.get_validator("ignore_missing"),
330 udc_json_load,
331 udc_lang_object, # expects {"en": "...", "fr": "..."} (fr optional)
332 udc_json_dump, # store as JSON string in extras
333 tk.get_converter("convert_to_extras"),
334 ],
335 }
336 )
337 else:
338 # Non-text
339 schema.update(
340 {
341 field: [
342 tk.get_validator("ignore_missing"),
343 tk.get_converter("convert_to_extras"),
344 ],
345 }
346 )
348 # ---- Multilingual core fields (replicate fluent_core_translated) ----
349 # title_translated / notes_translated store {lang: value} as JSON in extras,
350 # and keep `title` / `notes` as the default-locale strings.
352 schema.update(
353 {
354 "title_translated": [
355 tk.get_validator("ignore_missing"),
356 udc_json_load,
357 udc_seed_translated_from_core(
358 "title"
359 ), # seed from core title if missing
360 udc_lang_object,
361 udc_core_translated_to_extras("title"),
362 udc_json_dump,
363 tk.get_converter("convert_to_extras"),
364 ],
365 "notes_translated": [
366 tk.get_validator("ignore_missing"),
367 udc_json_load,
368 udc_seed_translated_from_core(
369 "notes"
370 ), # seed from core notes if missing
371 udc_lang_object,
372 udc_core_translated_to_extras("notes"),
373 udc_json_dump,
374 tk.get_converter("convert_to_extras"),
375 ],
376 # Accept: {"en": ["roads","transport"], "fr": ["routes","transport"]}
377 "tags_translated": [
378 tk.get_validator("ignore_missing"),
379 udc_json_load,
380 udc_lang_string_list,
381 udc_set_core_tags_from_translated, # set core 'tags' from tags_translated[default_lang]
382 udc_json_dump,
383 tk.get_converter("convert_to_extras"),
384 ],
385 "udc_import_extras": [
386 tk.get_validator("ignore_missing"),
387 udc_json_dump,
388 tk.get_converter("convert_to_extras"),
389 ],
390 }
391 )
393 schema.update(
394 {
395 # For import from other portals plugin
396 "cudc_import_config_id": [
397 tk.get_validator("ignore_missing"),
398 tk.get_converter("convert_to_extras"),
399 ],
400 # Keep track of the remote ID from the source portal
401 "cudc_import_remote_id": [
402 tk.get_validator("ignore_missing"),
403 tk.get_converter("convert_to_extras"),
404 ],
405 "is_unified": [
406 tk.get_validator("ignore_missing"),
407 tk.get_validator("boolean_validator"),
408 tk.get_converter("convert_to_extras"),
409 ],
410 "source_last_updated": [
411 tk.get_validator("ignore_missing"),
412 tk.get_converter("convert_to_extras"),
413 ],
414 # ---- chatgpt summary ------
415 "summary": [
416 tk.get_validator("ignore_missing"),
417 tk.get_converter("convert_to_extras"),
418 ],
419 "portal_type": [
420 tk.get_validator("ignore_missing"),
421 tk.get_converter("convert_to_extras"),
422 ],
423 }
424 )
425 return schema
427 def create_package_schema(self) -> Schema:
428 # let's grab the default schema in our plugin
429 schema: Schema = super(UdcPlugin, self).create_package_schema()
430 return self._modify_package_schema(schema)
432 def update_package_schema(self) -> Schema:
433 schema: Schema = super(UdcPlugin, self).update_package_schema()
434 # our custom field
435 return self._modify_package_schema(schema)
437 def show_package_schema(self) -> Schema:
438 schema: Schema = super(UdcPlugin, self).show_package_schema()
439 for field in self.all_fields:
440 if field in self.text_fields:
441 # Multilingual text: load JSON from extras
442 schema.update(
443 {
444 field: [
445 tk.get_converter("convert_from_extras"),
446 udc_json_load, # load JSON string from extras
447 tk.get_validator("ignore_missing"),
448 ],
449 }
450 )
451 else:
452 # Non-text: load directly from extras
453 schema.update(
454 {
455 field: [
456 tk.get_converter("convert_from_extras"),
457 tk.get_validator("ignore_missing"),
458 ],
459 }
460 )
462 # bring translated JSON back and ensure core fields are present
463 schema.update(
464 {
465 "title_translated": [
466 tk.get_converter("convert_from_extras"),
467 udc_json_load,
468 udc_fill_translated_from_core_on_show("title"),
469 tk.get_validator("ignore_missing"),
470 ],
471 "notes_translated": [
472 tk.get_converter("convert_from_extras"),
473 udc_json_load,
474 udc_fill_translated_from_core_on_show("notes"),
475 tk.get_validator("ignore_missing"),
476 ],
477 # Return tags_translated as a dict; if missing, seed from core tags
478 "tags_translated": [
479 tk.get_converter("convert_from_extras"),
480 udc_json_load,
481 udc_fill_tags_translated_from_core,
482 tk.get_validator("ignore_missing"),
483 ],
484 }
485 )
486 # ensure title/notes fallback to default locale from *_translated on show
487 schema.setdefault("title", []).append(udc_set_core_from_translated("title"))
488 schema.setdefault("notes", []).append(udc_set_core_from_translated("notes"))
490 schema.update(
491 {
492 # For import from other portals plugin
493 "cudc_import_config_id": [
494 tk.get_converter("convert_from_extras"),
495 tk.get_validator("ignore_missing"),
496 ],
497 # Keep track of the remote ID from the source portal
498 "cudc_import_remote_id": [
499 tk.get_converter("convert_from_extras"),
500 tk.get_validator("ignore_missing"),
501 ],
502 # For Unified Package:
503 # If the package is unified, it uses 'unified_has_versions' to link other packages.
504 # For every other packages that is not unified, use 'potential_duplicates' to link other duplicated packages.
505 "is_unified": [
506 tk.get_converter("convert_from_extras"),
507 tk.get_validator("ignore_missing"),
508 ],
509 "source_last_updated": [
510 tk.get_converter("convert_from_extras"),
511 tk.get_validator("ignore_missing"),
512 ],
513 # ---- chatgpt summary ------
514 "summary": [
515 tk.get_converter("convert_from_extras"),
516 tk.get_validator("ignore_missing"),
517 ],
518 "portal_type": [
519 tk.get_converter("convert_from_extras"),
520 tk.get_validator("ignore_missing"),
521 ],
522 # Version relations: decode JSON stored in extras into
523 # native dict/list structures for templates and Solr indexer.
524 "version_dataset": [
525 tk.get_converter("convert_from_extras"),
526 udc_json_load,
527 tk.get_validator("ignore_missing"),
528 ],
529 "dataset_versions": [
530 tk.get_converter("convert_from_extras"),
531 udc_json_load,
532 tk.get_validator("ignore_missing"),
533 ],
534 "udc_import_extras": [
535 tk.get_converter("convert_from_extras"),
536 udc_json_load,
537 tk.get_validator("ignore_missing"),
538 ],
539 }
540 )
542 return schema
544 def get_helpers(self):
545 langs = get_udc_langs()
546 language_labels = {}
548 for code in langs:
549 label = self._language_label(code, langs[0] if langs else "en")
550 language_labels[code] = label
552 return {
553 "render_markdown": render_markdown,
554 "config": self.maturity_model,
555 "maturity_model": self.maturity_model,
556 "facet_titles": self.facet_titles,
557 "facet_titles_raw": self.facet_titles_raw,
558 "maturity_model_text_fields": self.text_fields,
559 "udc_languages": langs,
560 "udc_default_lang": langs[0] if langs else "en",
561 "udc_language_labels": language_labels,
562 "get_default_facet_titles": get_default_facet_titles,
563 "process_facets_fields": process_facets_fields,
564 "humanize_entity_type": humanize_entity_type,
565 "get_maturity_percentages": get_maturity_percentages,
566 "get_system_info": get_system_info,
567 "udc_json_attr": udc_json_attr,
568 "license_options_details": license_options_details,
569 "pick_locale_with_fallback": pick_locale_with_fallback,
570 }
572 def _language_label(self, code: str, fallback: str) -> str:
573 """Return a human readable language name for templates."""
575 default_lang = (fallback or tk.config.get("ckan.locale_default", "en") or "en").replace("-", "_")
577 try:
578 display_locale = Locale.parse(default_lang)
579 target = Locale.parse(code.replace("-", "_"))
580 label = target.get_display_name(display_locale)
581 if isinstance(label, str) and label:
582 return label[0].upper() + label[1:]
583 except Exception:
584 pass
586 return code.upper()
588 def is_fallback(self):
589 # Return True to register this plugin as the default handler for
590 # package types not handled by any other IDatasetForm plugin.
591 return True
593 def package_types(self): # -> list[str]
594 # This plugin just registers itself as the 'catalogue'.
595 return ["dataset", "catalogue"]
597 def update_config_schema(self, schema: Schema):
599 ignore_missing = tk.get_validator("ignore_missing")
600 unicode_safe = tk.get_validator("unicode_safe")
601 json_object = tk.get_validator("json_object")
603 schema.update(
604 {
605 # This is a custom configuration option
606 "ckanext.udc.config": [
607 ignore_missing,
608 unicode_safe,
609 udc_config_validator,
610 ],
611 "ckanext.udc.group_side_panel_text": [ignore_missing, unicode_safe],
612 "ckanext.udc.organization_side_panel_text": [
613 ignore_missing,
614 unicode_safe,
615 ],
616 "ckanext.udc.dataset_side_panel_text": [ignore_missing, unicode_safe],
617 "ckanext.udc.catalogue_side_panel_text": [ignore_missing, unicode_safe],
618 # ---- chatgpt summary config ------
619 "ckanext.udc.desc.config": [ignore_missing, unicode_safe],
620 "ckanext.udc.maintenance_mode": [ignore_missing, unicode_safe],
621 }
622 )
624 return schema
626 def get_actions(self):
627 """
628 Override CKAN's default actions.
629 """
630 return {
631 "config_option_update": config_option_update,
632 "package_update": package_update,
633 "package_delete": package_delete,
634 # Custom Licenses
635 "license_create": license_create,
636 "license_delete": license_delete,
637 "licenses_get": licenses_get,
638 "license_update": license_update,
639 "test_long_task": test_long_task,
640 # Custom file format
641 "file_format_create": file_format_create,
642 "file_format_delete": file_format_delete,
643 "file_formats_get": file_formats_get,
644 # Chatgpt summary actions
645 "summary_generate": summary_generate,
646 "update_summary": update_summary,
647 "default_ai_summary_config": default_ai_summary_config,
648 # System actions
649 "reload_supervisord": reload_supervisord,
650 "get_system_stats": get_system_stats,
651 # Version metadata helper
652 "udc_version_meta": udc_version_meta,
653 # "maturity_model_get": get_maturity_model,
654 # Filters
655 "filter_facets_get": filter_facets_get,
656 # User management
657 "deleted_users_list": deleted_users_list,
658 "purge_deleted_users": purge_deleted_users,
659 "udc_user_list": udc_user_list,
660 "udc_user_reset_password": udc_user_reset_password,
661 "udc_user_delete": udc_user_delete,
662 # Organization management
663 "udc_organization_list": udc_organization_list,
664 "udc_deleted_organization_list": udc_deleted_organization_list,
665 "udc_organization_delete": udc_organization_delete,
666 "udc_purge_deleted_organizations": udc_purge_deleted_organizations,
667 "udc_organization_packages_list": udc_organization_packages_list,
668 "udc_organization_packages_ids": udc_organization_packages_ids,
669 "udc_organization_packages_delete": udc_organization_packages_delete,
670 }
672 def get_auth_functions(self):
673 """
674 Register custom authorization functions.
675 """
676 return {
677 "deleted_users_list": user_auth.deleted_users_list,
678 "purge_deleted_users": user_auth.purge_deleted_users,
679 "udc_user_list": user_auth.udc_user_list,
680 "udc_user_reset_password": user_auth.udc_user_reset_password,
681 "udc_user_delete": user_auth.udc_user_delete,
682 "udc_organization_list": organization_auth.udc_organization_list,
683 "udc_deleted_organization_list": organization_auth.udc_deleted_organization_list,
684 "udc_organization_delete": organization_auth.udc_organization_delete,
685 "udc_purge_deleted_organizations": organization_auth.udc_purge_deleted_organizations,
686 "udc_organization_packages_list": organization_auth.udc_organization_packages_list,
687 "udc_organization_packages_ids": organization_auth.udc_organization_packages_ids,
688 "udc_organization_packages_delete": organization_auth.udc_organization_packages_delete,
689 }
691 def dataset_facets(self, facets_dict: OrderedDict[str, Any], package_type: str):
692 for name in self.facet_titles:
693 if name == "portal_type":
694 facets_dict[name] = pick_locale(self.facet_titles_raw[name])
695 continue
696 if name in self.text_fields:
697 # The text fields (solr type=text) can be used as facets
698 # We need to use the builtin facet name
699 # and not the extras_ prefix
700 facets_dict[name] = pick_locale(self.facet_titles_raw[name])
701 else:
702 # We need the extras_ prefix for our custom solr schema
703 facets_dict["extras_" + name] = pick_locale(self.facet_titles_raw[name])
705 return facets_dict
707 def group_facets(
708 self,
709 facets_dict: OrderedDict[str, Any],
710 group_type: str,
711 package_type: Optional[str],
712 ):
713 return facets_dict
715 def organization_facets(
716 self,
717 facets_dict: OrderedDict[str, Any],
718 organization_type: str,
719 package_type: Optional[str],
720 ):
721 return facets_dict
723 def read(self, entity: "model.Package") -> None:
724 print(chalk.red("create"))
725 pass
727 def create(self, entity: "model.Package") -> None:
728 pass
730 def edit(self, entity: "model.Package") -> None:
731 pass
733 def delete(self, entity: "model.Package") -> None:
734 pass
736 def after_dataset_create(self, context: Context, pkg_dict: dict[str, Any]) -> None:
737 pass
739 def after_dataset_update(self, context: Context, pkg_dict: dict[str, Any]) -> None:
740 pass
742 def after_dataset_delete(self, context: Context, pkg_dict: dict[str, Any]) -> None:
743 pass
745 def after_dataset_show(self, context: Context, pkg_dict: dict[str, Any]) -> None:
746 if context.get("for_update"):
747 # Avoid injecting related_packages and udc_import_extras during package_update/patch.
748 pkg_dict.pop("udc_import_extras", None)
749 return
750 # Add related packages
751 related_packages = []
753 if pkg_dict.get("is_unified"):
754 rel = (
755 model.meta.Session.query(model.PackageRelationship)
756 .filter(model.PackageRelationship.subject_package_id == pkg_dict["id"])
757 .all()
758 )
759 for r in rel:
760 related_package = model.Package.get(r.object_package_id)
761 related_packages.append(
762 {
763 "title": related_package.title,
764 "id": related_package.id,
765 "name": related_package.name,
766 }
767 )
768 else:
769 # Non-unified package need to get the unified package and then get the related packages
770 rel = (
771 model.meta.Session.query(model.PackageRelationship)
772 .filter(model.PackageRelationship.object_package_id == pkg_dict["id"])
773 .all()
774 )
776 for r in rel:
777 unified_package = model.Package.get(r.subject_package_id)
778 related_packages.append(
779 {
780 "title": unified_package.title,
781 "id": unified_package.id,
782 "name": unified_package.name,
783 }
784 )
786 rel2 = (
787 model.meta.Session.query(model.PackageRelationship)
788 .filter(
789 model.PackageRelationship.subject_package_id
790 == unified_package.id
791 )
792 .filter(
793 model.PackageRelationship.object_package_id != pkg_dict["id"]
794 )
795 .all()
796 )
798 for r in rel2:
799 related_package = model.Package.get(r.object_package_id)
800 # Check if exists
801 if related_package.id not in set(
802 [p["id"] for p in related_packages]
803 ):
804 related_packages.append(
805 {
806 "title": related_package.title,
807 "id": related_package.id,
808 "name": related_package.name,
809 }
810 )
812 pkg_dict["related_packages"] = related_packages
814 def before_dataset_search(self, params: dict[str, Any]) -> dict[str, Any]:
815 # print(chalk.red("before_dataset_search"))
816 # print(search_params)
817 # L = get_current_lang()
818 # default = get_udc_langs()[0]
820 # qf = [
821 # f"title_{L}_txt^6",
822 # f"notes_{L}_txt^3",
823 # # include if you indexed tags_{L}_txt in index.py:
824 # f"tags_{L}_txt^1",
825 # ]
826 # for name in self.text_fields:
827 # qf.append(f"{name}_{L}_txt^2")
829 # if L != default:
830 # qf += [
831 # f"title_{default}_txt^2",
832 # f"notes_{default}_txt^1",
833 # # f"tags_{default}_txt^0.5",
834 # ]
835 # for name in self.text_fields:
836 # qf.append(f"{name}_{default}_txt^1")
838 # params['defType'] = 'edismax'
839 # params['qf'] = ' '.join(qf)
840 # params.setdefault('pf', f"title_{L}_txt^8")
841 # params.setdefault('qs', '2')
842 return params
844 def after_dataset_search(
845 self, search_results: dict[str, Any], search_params: dict[str, Any]
846 ) -> dict[str, Any]:
847 return search_results
849 def before_dataset_index(self, pkg_dict: dict[str, Any]) -> dict[str, Any]:
850 return _before_dataset_index(pkg_dict)
852 def before_dataset_view(self, pkg_dict: dict[str, Any]) -> dict[str, Any]:
853 return pkg_dict
855 # IMiddleware
856 def make_middleware(self, app: CKANApp, config: CKANConfig) -> CKANApp:
857 override_error_handler(app, config)
858 return app
860 def make_error_log_middleware(self, app, config: CKANConfig) -> CKANApp:
861 return app
863 def get_validators(self):
864 return {
865 "udc_lang_object": udc_lang_object,
866 "udc_json_dump": udc_json_dump,
867 "udc_json_load": udc_json_load,
868 }