Coverage for ckanext/udc/plugin.py: 80%

278 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2026-01-19 23:48 +0000

1from __future__ import annotations 

2 

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 

20 

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 

35 

36from flask import Blueprint 

37 

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 

58 

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 

84 

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.user import auth as user_auth 

94from .i18n import ( 

95 udc_lang_object, 

96 udc_json_dump, 

97 udc_json_load, 

98 udc_core_translated_to_extras, 

99 udc_set_core_from_translated, 

100 udc_lang_string_list, 

101 udc_set_core_tags_from_translated, 

102 udc_fill_tags_translated_from_core, 

103 udc_seed_translated_from_core, 

104 udc_fill_translated_from_core_on_show, 

105) 

106 

107 

108""" 

109See https://docs.ckan.org/en/latest/theming/templates.html 

110See https://docs.ckan.org/en/latest/extensions/adding-custom-fields.html 

111See https://docs.ckan.org/en/2.10/extensions/remote-config-update.html 

112See https://docs.ckan.org/en/2.10/extensions/custom-config-settings.html?highlight=config%20declaration 

113See https://docs.ckan.org/en/2.10/theming/webassets.html 

114""" 

115log = logging.getLogger(__name__) 

116 

117# Add UDC CLI 

118# We can then `ckan -c /etc/ckan/default/ckan.ini udc move-to-catalogues` to run the migration script 

119if hasattr(ckan, "cli") and hasattr(ckan.cli, "cli"): 

120 ckan.cli.cli.ckan.add_command(cli_udc.udc) 

121 

122 

123@tk.blanket.blueprints 

124class UdcPlugin(plugins.SingletonPlugin, tk.DefaultDatasetForm, DefaultTranslation): 

125 plugins.implements(plugins.IConfigurer) 

126 plugins.implements(plugins.IConfigurable) 

127 plugins.implements(plugins.IDatasetForm) 

128 plugins.implements(plugins.ITemplateHelpers) 

129 plugins.implements(plugins.IActions) 

130 plugins.implements(plugins.IAuthFunctions) 

131 plugins.implements(plugins.IFacets) 

132 plugins.implements(plugins.IPackageController) 

133 plugins.implements(plugins.IMiddleware) 

134 plugins.implements(plugins.IBlueprint) 

135 plugins.implements(plugins.IValidators) 

136 plugins.implements(plugins.ITranslation) 

137 

138 disable_graphdb = False 

139 maturity_model = [] 

140 mappings = {} 

141 preload_ontologies = {} 

142 all_fields: List[str] = [] # This does not contain core CKAN fields 

143 facet_titles = {} 

144 facet_titles_raw = {} # multilingual titles, not picked to current locale 

145 text_fields: List[str] = [] 

146 date_fields: List[str] = [] 

147 multiple_select_fields: List[str] = [] 

148 dropdown_options: dict[str, dict[str, str]] = {} 

149 

150 def update_config(self, config_): 

151 tk.add_template_directory(config_, "templates") 

152 tk.add_public_directory(config_, "public") 

153 tk.add_resource("assets", "udc") 

154 

155 def _cli_configure(self): 

156 """Partial load for CLI""" 

157 self.disable_graphdb = True 

158 existing_config = ckan.model.system_info.get_system_info("ckanext.udc.config") 

159 if existing_config: 

160 try: 

161 # Call our plugin to update the config 

162 self.reload_config(json.loads(existing_config)) 

163 except: 

164 log.error 

165 

166 # Load custom licenses 

167 init_licenses() 

168 

169 def configure(self, config: CKANConfig): 

170 log.info(sys.argv) 

171 if not ( 

172 "run" in sys.argv 

173 or "uwsgi" in sys.argv 

174 or ("jobs" in sys.argv and "worker" in sys.argv) 

175 or ("search-index" in sys.argv) 

176 or ("translation" in sys.argv) 

177 or ("asset" in sys.argv) 

178 ): 

179 log.info("Skipping UDC Plugin Configuration") 

180 # Do not load the plugin if we are running the CLI 

181 self._cli_configure() 

182 return 

183 existing_config = ckan.model.system_info.get_system_info("ckanext.udc.config") 

184 # print(existing_config) 

185 

186 # Load sparql client 

187 endpoint = tk.config.get("udc.sparql.endpoint") 

188 username = tk.config.get("udc.sparql.username") or None 

189 password = tk.config.get("udc.sparql.password") or None 

190 

191 if endpoint is None: 

192 self.disable_graphdb = True 

193 log.info("No GraphDB Endpoint is provided.") 

194 

195 else: 

196 self.sparql_client = SparqlClient( 

197 endpoint, username=username, password=password 

198 ) 

199 if self.sparql_client.test_connecetion(): 

200 log.info("GraphDB connected: " + endpoint) 

201 else: 

202 log.error("UDC cannot connect to the GraphDB") 

203 self.disable_graphdb = True 

204 

205 # Load config 

206 if existing_config: 

207 try: 

208 # Call our plugin to update the config 

209 self.reload_config(json.loads(existing_config)) 

210 except: 

211 log.error 

212 

213 # Load custom licenses 

214 init_licenses() 

215 

216 # Init chatgpt summary plugin 

217 init_udc_desc() 

218 

219 log.info("UDC Plugin Loaded!") 

220 

221 def reload_config(self, config: list): 

222 try: 

223 # log.info("tring to load udc config:") 

224 # log.info(config) 

225 all_fields = [] 

226 self.facet_titles.clear() 

227 self.facet_titles_raw.clear() 

228 self.text_fields.clear() 

229 self.date_fields.clear() 

230 self.multiple_select_fields.clear() 

231 self.dropdown_options.clear() 

232 for level in config["maturity_model"]: 

233 for field in level["fields"]: 

234 if field.get("name"): 

235 all_fields.append(field["name"]) 

236 type = field.get("type") 

237 if field.get("name") and ( 

238 type == "" 

239 or type is None 

240 or type == "text" 

241 or type == "single_select" 

242 or type == "multiple_select" 

243 or type == "number" 

244 or type == "date" 

245 ): 

246 self.facet_titles[field["name"]] = tk._(pick_locale(field["label"], 'en')) 

247 self.facet_titles_raw[field["name"]] = field["label"] 

248 if field.get("name") and (type == "text" or type is None): 

249 self.text_fields.append(field["name"]) 

250 if field.get("type") == "date": 

251 self.date_fields.append(field["name"]) 

252 if field.get("type") == "multiple_select": 

253 self.multiple_select_fields.append(field["name"]) 

254 

255 # Preload ontologies 

256 if not self.disable_graphdb: 

257 endpoint = tk.config.get("udc.sparql.endpoint") 

258 username = tk.config.get("udc.sparql.username") or None 

259 password = tk.config.get("udc.sparql.password") or None 

260 # This will preload ontologies and 

261 # populate options to the fields that uses 'optionsFromQuery' 

262 preload_ontologies( 

263 config, endpoint, username, password, self.sparql_client 

264 ) 

265 

266 # Store dropdown options 

267 for level in config["maturity_model"]: 

268 for field in level["fields"]: 

269 if ( 

270 field.get("type") == "multiple_select" 

271 or field.get("type") == "single_select" 

272 ): 

273 options = self.dropdown_options[field["name"]] = {} 

274 for option in field["options"]: 

275 options[option["value"]] = tk._(option["text"]) 

276 log.info( 

277 f"Dropdown options for field {field['name']} loaded: {len(options.keys())}" 

278 ) 

279 

280 # Update solr index 

281 update_solr_maturity_model_fields(config["maturity_model"]) 

282 

283 # Do not mutate the vars 

284 self.all_fields.clear() 

285 self.all_fields.extend(all_fields) 

286 self.maturity_model.clear() 

287 self.maturity_model.extend(config["maturity_model"]) 

288 self.mappings.clear() 

289 self.mappings.update(config["mappings"]) 

290 self.preload_ontologies.clear() 

291 self.preload_ontologies.update(config["preload_ontologies"]) 

292 

293 except Exception as e: 

294 log.error("UDC Plugin Error:") 

295 traceback.print_exc() 

296 

297 def _modify_package_schema(self, schema: Schema) -> Schema: 

298 """ 

299 Wire CUDC custom fields into CKAN: 

300 - For type="text" custom fields: accept multilingual {lang: value} and store as JSON string in extras. 

301 - For other types (date/number/select/etc.): keep original convert_to_extras for now. 

302 """ 

303 # our custom fields 

304 for field in self.all_fields: 

305 if field in self.text_fields: 

306 # Multilingual text: validate object -> dump JSON -> extras 

307 schema.update( 

308 { 

309 field: [ 

310 tk.get_validator("ignore_missing"), 

311 udc_json_load, 

312 udc_lang_object, # expects {"en": "...", "fr": "..."} (fr optional) 

313 udc_json_dump, # store as JSON string in extras 

314 tk.get_converter("convert_to_extras"), 

315 ], 

316 } 

317 ) 

318 else: 

319 # Non-text 

320 schema.update( 

321 { 

322 field: [ 

323 tk.get_validator("ignore_missing"), 

324 tk.get_converter("convert_to_extras"), 

325 ], 

326 } 

327 ) 

328 

329 # ---- Multilingual core fields (replicate fluent_core_translated) ---- 

330 # title_translated / notes_translated store {lang: value} as JSON in extras, 

331 # and keep `title` / `notes` as the default-locale strings. 

332 

333 schema.update( 

334 { 

335 "title_translated": [ 

336 tk.get_validator("ignore_missing"), 

337 udc_json_load, 

338 udc_seed_translated_from_core( 

339 "title" 

340 ), # seed from core title if missing 

341 udc_lang_object, 

342 udc_core_translated_to_extras("title"), 

343 udc_json_dump, 

344 tk.get_converter("convert_to_extras"), 

345 ], 

346 "notes_translated": [ 

347 tk.get_validator("ignore_missing"), 

348 udc_json_load, 

349 udc_seed_translated_from_core( 

350 "notes" 

351 ), # seed from core notes if missing 

352 udc_lang_object, 

353 udc_core_translated_to_extras("notes"), 

354 udc_json_dump, 

355 tk.get_converter("convert_to_extras"), 

356 ], 

357 # Accept: {"en": ["roads","transport"], "fr": ["routes","transport"]} 

358 "tags_translated": [ 

359 tk.get_validator("ignore_missing"), 

360 udc_json_load, 

361 udc_lang_string_list, 

362 udc_set_core_tags_from_translated, # set core 'tags' from tags_translated[default_lang] 

363 udc_json_dump, 

364 tk.get_converter("convert_to_extras"), 

365 ], 

366 "udc_import_extras": [ 

367 tk.get_validator("ignore_missing"), 

368 udc_json_dump, 

369 tk.get_converter("convert_to_extras"), 

370 ], 

371 } 

372 ) 

373 

374 schema.update( 

375 { 

376 # For import from other portals plugin 

377 "cudc_import_config_id": [ 

378 tk.get_validator("ignore_missing"), 

379 tk.get_converter("convert_to_extras"), 

380 ], 

381 # Keep track of the remote ID from the source portal 

382 "cudc_import_remote_id": [ 

383 tk.get_validator("ignore_missing"), 

384 tk.get_converter("convert_to_extras"), 

385 ], 

386 "is_unified": [ 

387 tk.get_validator("ignore_missing"), 

388 tk.get_validator("boolean_validator"), 

389 tk.get_converter("convert_to_extras"), 

390 ], 

391 "source_last_updated": [ 

392 tk.get_validator("ignore_missing"), 

393 tk.get_converter("convert_to_extras"), 

394 ], 

395 # ---- chatgpt summary ------ 

396 "summary": [ 

397 tk.get_validator("ignore_missing"), 

398 tk.get_converter("convert_to_extras"), 

399 ], 

400 } 

401 ) 

402 return schema 

403 

404 def create_package_schema(self) -> Schema: 

405 # let's grab the default schema in our plugin 

406 schema: Schema = super(UdcPlugin, self).create_package_schema() 

407 return self._modify_package_schema(schema) 

408 

409 def update_package_schema(self) -> Schema: 

410 schema: Schema = super(UdcPlugin, self).update_package_schema() 

411 # our custom field 

412 return self._modify_package_schema(schema) 

413 

414 def show_package_schema(self) -> Schema: 

415 schema: Schema = super(UdcPlugin, self).show_package_schema() 

416 for field in self.all_fields: 

417 if field in self.text_fields: 

418 # Multilingual text: load JSON from extras 

419 schema.update( 

420 { 

421 field: [ 

422 tk.get_converter("convert_from_extras"), 

423 udc_json_load, # load JSON string from extras 

424 tk.get_validator("ignore_missing"), 

425 ], 

426 } 

427 ) 

428 else: 

429 # Non-text: load directly from extras 

430 schema.update( 

431 { 

432 field: [ 

433 tk.get_converter("convert_from_extras"), 

434 tk.get_validator("ignore_missing"), 

435 ], 

436 } 

437 ) 

438 

439 # bring translated JSON back and ensure core fields are present 

440 schema.update( 

441 { 

442 "title_translated": [ 

443 tk.get_converter("convert_from_extras"), 

444 udc_json_load, 

445 udc_fill_translated_from_core_on_show("title"), 

446 tk.get_validator("ignore_missing"), 

447 ], 

448 "notes_translated": [ 

449 tk.get_converter("convert_from_extras"), 

450 udc_json_load, 

451 udc_fill_translated_from_core_on_show("notes"), 

452 tk.get_validator("ignore_missing"), 

453 ], 

454 # Return tags_translated as a dict; if missing, seed from core tags 

455 "tags_translated": [ 

456 tk.get_converter("convert_from_extras"), 

457 udc_json_load, 

458 udc_fill_tags_translated_from_core, 

459 tk.get_validator("ignore_missing"), 

460 ], 

461 } 

462 ) 

463 # ensure title/notes fallback to default locale from *_translated on show 

464 schema.setdefault("title", []).append(udc_set_core_from_translated("title")) 

465 schema.setdefault("notes", []).append(udc_set_core_from_translated("notes")) 

466 

467 schema.update( 

468 { 

469 # For import from other portals plugin 

470 "cudc_import_config_id": [ 

471 tk.get_converter("convert_from_extras"), 

472 tk.get_validator("ignore_missing"), 

473 ], 

474 # Keep track of the remote ID from the source portal 

475 "cudc_import_remote_id": [ 

476 tk.get_converter("convert_from_extras"), 

477 tk.get_validator("ignore_missing"), 

478 ], 

479 # For Unified Package: 

480 # If the package is unified, it uses 'unified_has_versions' to link other packages. 

481 # For every other packages that is not unified, use 'potential_duplicates' to link other duplicated packages. 

482 "is_unified": [ 

483 tk.get_converter("convert_from_extras"), 

484 tk.get_validator("ignore_missing"), 

485 ], 

486 "source_last_updated": [ 

487 tk.get_converter("convert_from_extras"), 

488 tk.get_validator("ignore_missing"), 

489 ], 

490 # ---- chatgpt summary ------ 

491 "summary": [ 

492 tk.get_converter("convert_from_extras"), 

493 tk.get_validator("ignore_missing"), 

494 ], 

495 # Version relations: decode JSON stored in extras into 

496 # native dict/list structures for templates and Solr indexer. 

497 "version_dataset": [ 

498 tk.get_converter("convert_from_extras"), 

499 udc_json_load, 

500 tk.get_validator("ignore_missing"), 

501 ], 

502 "dataset_versions": [ 

503 tk.get_converter("convert_from_extras"), 

504 udc_json_load, 

505 tk.get_validator("ignore_missing"), 

506 ], 

507 "udc_import_extras": [ 

508 tk.get_converter("convert_from_extras"), 

509 udc_json_load, 

510 tk.get_validator("ignore_missing"), 

511 ], 

512 } 

513 ) 

514 

515 return schema 

516 

517 def get_helpers(self): 

518 langs = get_udc_langs() 

519 language_labels = {} 

520 

521 for code in langs: 

522 label = self._language_label(code, langs[0] if langs else "en") 

523 language_labels[code] = label 

524 

525 return { 

526 "render_markdown": render_markdown, 

527 "config": self.maturity_model, 

528 "maturity_model": self.maturity_model, 

529 "facet_titles": self.facet_titles, 

530 "facet_titles_raw": self.facet_titles_raw, 

531 "maturity_model_text_fields": self.text_fields, 

532 "udc_languages": langs, 

533 "udc_default_lang": langs[0] if langs else "en", 

534 "udc_language_labels": language_labels, 

535 "get_default_facet_titles": get_default_facet_titles, 

536 "process_facets_fields": process_facets_fields, 

537 "humanize_entity_type": humanize_entity_type, 

538 "get_maturity_percentages": get_maturity_percentages, 

539 "get_system_info": get_system_info, 

540 "udc_json_attr": udc_json_attr, 

541 "license_options_details": license_options_details, 

542 "pick_locale_with_fallback": pick_locale_with_fallback, 

543 } 

544 

545 def _language_label(self, code: str, fallback: str) -> str: 

546 """Return a human readable language name for templates.""" 

547 

548 default_lang = (fallback or tk.config.get("ckan.locale_default", "en") or "en").replace("-", "_") 

549 

550 try: 

551 display_locale = Locale.parse(default_lang) 

552 target = Locale.parse(code.replace("-", "_")) 

553 label = target.get_display_name(display_locale) 

554 if isinstance(label, str) and label: 

555 return label[0].upper() + label[1:] 

556 except Exception: 

557 pass 

558 

559 return code.upper() 

560 

561 def is_fallback(self): 

562 # Return True to register this plugin as the default handler for 

563 # package types not handled by any other IDatasetForm plugin. 

564 return True 

565 

566 def package_types(self): # -> list[str] 

567 # This plugin just registers itself as the 'catalogue'. 

568 return ["dataset", "catalogue"] 

569 

570 def update_config_schema(self, schema: Schema): 

571 

572 ignore_missing = tk.get_validator("ignore_missing") 

573 unicode_safe = tk.get_validator("unicode_safe") 

574 json_object = tk.get_validator("json_object") 

575 

576 schema.update( 

577 { 

578 # This is a custom configuration option 

579 "ckanext.udc.config": [ 

580 ignore_missing, 

581 unicode_safe, 

582 udc_config_validator, 

583 ], 

584 "ckanext.udc.group_side_panel_text": [ignore_missing, unicode_safe], 

585 "ckanext.udc.organization_side_panel_text": [ 

586 ignore_missing, 

587 unicode_safe, 

588 ], 

589 "ckanext.udc.dataset_side_panel_text": [ignore_missing, unicode_safe], 

590 "ckanext.udc.catalogue_side_panel_text": [ignore_missing, unicode_safe], 

591 # ---- chatgpt summary config ------ 

592 "ckanext.udc.desc.config": [ignore_missing, unicode_safe], 

593 } 

594 ) 

595 

596 return schema 

597 

598 def get_actions(self): 

599 """ 

600 Override CKAN's default actions. 

601 """ 

602 return { 

603 "config_option_update": config_option_update, 

604 "package_update": package_update, 

605 "package_delete": package_delete, 

606 # Custom Licenses 

607 "license_create": license_create, 

608 "license_delete": license_delete, 

609 "licenses_get": licenses_get, 

610 "license_update": license_update, 

611 "test_long_task": test_long_task, 

612 # Custom file format 

613 "file_format_create": file_format_create, 

614 "file_format_delete": file_format_delete, 

615 "file_formats_get": file_formats_get, 

616 # Chatgpt summary actions 

617 "summary_generate": summary_generate, 

618 "update_summary": update_summary, 

619 "default_ai_summary_config": default_ai_summary_config, 

620 # System actions 

621 "reload_supervisord": reload_supervisord, 

622 "get_system_stats": get_system_stats, 

623 # Version metadata helper 

624 "udc_version_meta": udc_version_meta, 

625 # "maturity_model_get": get_maturity_model, 

626 # Filters 

627 "filter_facets_get": filter_facets_get, 

628 # User management 

629 "deleted_users_list": deleted_users_list, 

630 "purge_deleted_users": purge_deleted_users, 

631 "udc_user_list": udc_user_list, 

632 "udc_user_reset_password": udc_user_reset_password, 

633 "udc_user_delete": udc_user_delete, 

634 } 

635 

636 def get_auth_functions(self): 

637 """ 

638 Register custom authorization functions. 

639 """ 

640 return { 

641 "deleted_users_list": user_auth.deleted_users_list, 

642 "purge_deleted_users": user_auth.purge_deleted_users, 

643 "udc_user_list": user_auth.udc_user_list, 

644 "udc_user_reset_password": user_auth.udc_user_reset_password, 

645 "udc_user_delete": user_auth.udc_user_delete, 

646 } 

647 

648 def dataset_facets(self, facets_dict: OrderedDict[str, Any], package_type: str): 

649 for name in self.facet_titles: 

650 if name in self.text_fields: 

651 # The text fields (solr type=text) can be used as facets 

652 # We need to use the builtin facet name 

653 # and not the extras_ prefix 

654 facets_dict[name] = pick_locale(self.facet_titles_raw[name]) 

655 else: 

656 # We need the extras_ prefix for our custom solr schema 

657 facets_dict["extras_" + name] = pick_locale(self.facet_titles_raw[name]) 

658 

659 return facets_dict 

660 

661 def group_facets( 

662 self, 

663 facets_dict: OrderedDict[str, Any], 

664 group_type: str, 

665 package_type: Optional[str], 

666 ): 

667 return facets_dict 

668 

669 def organization_facets( 

670 self, 

671 facets_dict: OrderedDict[str, Any], 

672 organization_type: str, 

673 package_type: Optional[str], 

674 ): 

675 return facets_dict 

676 

677 def read(self, entity: "model.Package") -> None: 

678 print(chalk.red("create")) 

679 pass 

680 

681 def create(self, entity: "model.Package") -> None: 

682 pass 

683 

684 def edit(self, entity: "model.Package") -> None: 

685 pass 

686 

687 def delete(self, entity: "model.Package") -> None: 

688 pass 

689 

690 def after_dataset_create(self, context: Context, pkg_dict: dict[str, Any]) -> None: 

691 pass 

692 

693 def after_dataset_update(self, context: Context, pkg_dict: dict[str, Any]) -> None: 

694 pass 

695 

696 def after_dataset_delete(self, context: Context, pkg_dict: dict[str, Any]) -> None: 

697 pass 

698 

699 def after_dataset_show(self, context: Context, pkg_dict: dict[str, Any]) -> None: 

700 if context.get("for_update"): 

701 # Avoid injecting related_packages and udc_import_extras during package_update/patch. 

702 pkg_dict.pop("udc_import_extras", None) 

703 return 

704 # Add related packages 

705 related_packages = [] 

706 

707 if pkg_dict.get("is_unified"): 

708 rel = ( 

709 model.meta.Session.query(model.PackageRelationship) 

710 .filter(model.PackageRelationship.subject_package_id == pkg_dict["id"]) 

711 .all() 

712 ) 

713 for r in rel: 

714 related_package = model.Package.get(r.object_package_id) 

715 related_packages.append( 

716 { 

717 "title": related_package.title, 

718 "id": related_package.id, 

719 "name": related_package.name, 

720 } 

721 ) 

722 else: 

723 # Non-unified package need to get the unified package and then get the related packages 

724 rel = ( 

725 model.meta.Session.query(model.PackageRelationship) 

726 .filter(model.PackageRelationship.object_package_id == pkg_dict["id"]) 

727 .all() 

728 ) 

729 

730 for r in rel: 

731 unified_package = model.Package.get(r.subject_package_id) 

732 related_packages.append( 

733 { 

734 "title": unified_package.title, 

735 "id": unified_package.id, 

736 "name": unified_package.name, 

737 } 

738 ) 

739 

740 rel2 = ( 

741 model.meta.Session.query(model.PackageRelationship) 

742 .filter( 

743 model.PackageRelationship.subject_package_id 

744 == unified_package.id 

745 ) 

746 .filter( 

747 model.PackageRelationship.object_package_id != pkg_dict["id"] 

748 ) 

749 .all() 

750 ) 

751 

752 for r in rel2: 

753 related_package = model.Package.get(r.object_package_id) 

754 # Check if exists 

755 if related_package.id not in set( 

756 [p["id"] for p in related_packages] 

757 ): 

758 related_packages.append( 

759 { 

760 "title": related_package.title, 

761 "id": related_package.id, 

762 "name": related_package.name, 

763 } 

764 ) 

765 

766 pkg_dict["related_packages"] = related_packages 

767 

768 def before_dataset_search(self, params: dict[str, Any]) -> dict[str, Any]: 

769 # print(chalk.red("before_dataset_search")) 

770 # print(search_params) 

771 # L = get_current_lang() 

772 # default = get_udc_langs()[0] 

773 

774 # qf = [ 

775 # f"title_{L}_txt^6", 

776 # f"notes_{L}_txt^3", 

777 # # include if you indexed tags_{L}_txt in index.py: 

778 # f"tags_{L}_txt^1", 

779 # ] 

780 # for name in self.text_fields: 

781 # qf.append(f"{name}_{L}_txt^2") 

782 

783 # if L != default: 

784 # qf += [ 

785 # f"title_{default}_txt^2", 

786 # f"notes_{default}_txt^1", 

787 # # f"tags_{default}_txt^0.5", 

788 # ] 

789 # for name in self.text_fields: 

790 # qf.append(f"{name}_{default}_txt^1") 

791 

792 # params['defType'] = 'edismax' 

793 # params['qf'] = ' '.join(qf) 

794 # params.setdefault('pf', f"title_{L}_txt^8") 

795 # params.setdefault('qs', '2') 

796 return params 

797 

798 def after_dataset_search( 

799 self, search_results: dict[str, Any], search_params: dict[str, Any] 

800 ) -> dict[str, Any]: 

801 return search_results 

802 

803 def before_dataset_index(self, pkg_dict: dict[str, Any]) -> dict[str, Any]: 

804 return _before_dataset_index(pkg_dict) 

805 

806 def before_dataset_view(self, pkg_dict: dict[str, Any]) -> dict[str, Any]: 

807 return pkg_dict 

808 

809 # IMiddleware 

810 def make_middleware(self, app: CKANApp, config: CKANConfig) -> CKANApp: 

811 override_error_handler(app, config) 

812 return app 

813 

814 def make_error_log_middleware(self, app, config: CKANConfig) -> CKANApp: 

815 return app 

816 

817 def get_validators(self): 

818 return { 

819 "udc_lang_object": udc_lang_object, 

820 "udc_json_dump": udc_json_dump, 

821 "udc_json_load": udc_json_load, 

822 }