From 581a4da0c3dd8bf92184b347f854a9ca9ccd4258 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Sat, 18 Jul 2026 18:39:40 -0400 Subject: [PATCH 1/5] edit_using__id feature for pd preview (depr) and form --- ckanext/canada/helpers.py | 3 ++ .../recombinant/update_pd_record.html | 12 +++++- .../templates/snippets/pd_datatable_depr.html | 1 + ckanext/canada/view.py | 39 ++++++++++++++----- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/ckanext/canada/helpers.py b/ckanext/canada/helpers.py index 4e3d82b82..3753206e7 100755 --- a/ckanext/canada/helpers.py +++ b/ckanext/canada/helpers.py @@ -482,6 +482,9 @@ def get_pd_datatable(resource_name: str, activity_priority = 1 fields = [] fids = [] + if chromo['edit_using__id']: + fields.append({'type': 'int', 'id': '_id', 'label': 'open_canada_id', 'priority': 0}) + fids.append('_id') for f in chromo['fields']: if f.get('published_resource_computed_field'): continue diff --git a/ckanext/canada/templates/recombinant/update_pd_record.html b/ckanext/canada/templates/recombinant/update_pd_record.html index 36042c2d5..6bcc6e3b3 100644 --- a/ckanext/canada/templates/recombinant/update_pd_record.html +++ b/ckanext/canada/templates/recombinant/update_pd_record.html @@ -41,6 +41,16 @@ {%- set chromo = h.recombinant_get_chromo(resource_name) -%}
+ {%- if chromo.edit_using__id -%} + {% snippet 'scheming/form_snippets/text.html', + field={ + 'field_name': '_id', + 'label': 'open_canada_id', + 'form_attrs': {'readonly':''}, + 'required': true, + 'help_text': _('open.canada.ca unique record identifier'), + }, data=data, errors={} %} + {%- endif -%} {%- for f in chromo.fields %} {%- if f.get('import_template_include', True) and not f.get('published_resource_computed_field', false) %} {%- set required = f.get('form_required', False) %} @@ -50,7 +60,7 @@ {%- set form_attrs = f.get('form_attrs', {}) %} {%- set form_select_attrs = f.get('form_select_attrs', {}) %} {%- set form_snippet = f.get('form_snippet', 'scheming/form_snippets/text.html') %} - {%- if f.datastore_id in pk_fields %} + {%- if not chromo.edit_using__id and f.datastore_id in pk_fields %} {%- set form_attrs = dict(form_attrs, readonly='') %} {%- endif %} {%- if f.datastore_id in choice_fields %} diff --git a/ckanext/canada/templates/snippets/pd_datatable_depr.html b/ckanext/canada/templates/snippets/pd_datatable_depr.html index a5f1e815f..89d660ba2 100644 --- a/ckanext/canada/templates/snippets/pd_datatable_depr.html +++ b/ckanext/canada/templates/snippets/pd_datatable_depr.html @@ -157,6 +157,7 @@

{{_("Preview")}}

{% if chromo.edit_form and can_edit %} {"className": "bg-info", "orderable": false, "targets": "no-sort"}, {% endif %} + {% if chromo.edit_using__id %}null,{% endif %} {% for f in chromo.fields %} {% if not f.get('published_resource_computed_field', false) %} {{- "null" if 'preview_class' not in f else '{"className":"' | safe ~ f['preview_class'] ~ '"}' | safe }} diff --git a/ckanext/canada/view.py b/ckanext/canada/view.py index 5a95ea71f..49b80d206 100644 --- a/ckanext/canada/view.py +++ b/ckanext/canada/view.py @@ -531,7 +531,11 @@ def update_pd_record(owner_org: str, resource_name: str, pk: str): choice_fields = _get_choice_fields(resource_name) pk_fields = aslist(chromo['datastore_primary_key']) - pk_filter = dict(zip(pk_fields, pk_list)) + if chromo.get('edit_using__id'): + pk_fields = ['_id'] + pk_filter = {'_id': pk_list[0]} + else: + pk_filter = dict(zip(pk_fields, pk_list)) records = lc.action.datastore_search( resource_id=res['id'], @@ -559,22 +563,29 @@ def update_pd_record(owner_org: str, resource_name: str, pk: str): choice_fields) error_summary = None # can't change pk fields - for f_id in data: - if f_id in pk_fields: - data[f_id] = record[f_id] + for f_id in pk_fields: + data[f_id] = record[f_id] try: lc.action.datastore_upsert( resource_id=res['id'], - # method='update', FIXME not raising ValidationErrors + method='update', records=[{k: None if k in err else v for (k, v) in data.items()}], dry_run=bool(err)) except ValidationError as ve: try: + if ( + 'duplicate key value violates unique constraint' in + ve.error_dict['records'][0]): # type: ignore + err = dict({ + k: [_("This record already exists")] + for k in aslist(chromo['datastore_primary_key']) + }, **err) # type_ignore_reason: incomplete typing - err = dict({ - k: list(format_trigger_error(v)) - for (k, v) in ve.error_dict['records'][0].items() # type: ignore - }, **err) + else: + err = dict({ + k: list(format_trigger_error(v)) + for (k, v) in ve.error_dict['records'][0].items() # type: ignore + }, **err) except AttributeError: log.warning('Failed to update %s record for org %s:\n%s', resource_name, owner_org, traceback.format_exc()) @@ -604,6 +615,8 @@ def update_pd_record(owner_org: str, resource_name: str, pk: str): ) data = {} + if chromo.get('edit_using__id'): + data['_id'] = pk_list[0] for f in chromo['fields']: if ( not f.get('import_template_include', True) or @@ -659,6 +672,8 @@ def upsert_pd_data(owner_org: str, resource_name: str): lc = LocalCKAN() chromo = h.recombinant_get_chromo(resource_name) pk_fields = aslist(chromo['datastore_primary_key']) + if chromo.get('edit_using__id'): + pk_fields = ['_id'] offset = 0 records = data_dict.get('records', []) resource_id = data_dict.get('resource_id') @@ -1151,6 +1166,9 @@ def datatable(resource_name: str, resource_id: str): can_edit = h.check_access('resource_update', {'id': resource_id}) cols = [] fids = [] + if chromo.get('edit_using__id'): + cols.append('_id') + fids.append('_id') for f in chromo['fields']: if f.get('published_resource_computed_field', False): continue @@ -1198,7 +1216,8 @@ def datatable(resource_name: str, resource_id: str): 'canada.update_pd_record', owner_org=pkg['organization']['name'], resource_name=resource_name, - pk=','.join(_url_part_escape(row[i+1]) for i in pkids) + pk=_url_part_escape(row[1]) if chromo.get('edit_using__id') else + ','.join(_url_part_escape(row[i+1]) for i in pkids) ) ) ) From 8edd4d1df3ba0591dd5e7c639b14bd36d48d3364 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Sun, 19 Jul 2026 21:54:12 -0400 Subject: [PATCH 2/5] edit_using__id feature for excel templates --- ckanext/canada/helpers.py | 7 ++++++- ckanext/canada/templates/snippets/pd_datatable_depr.html | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ckanext/canada/helpers.py b/ckanext/canada/helpers.py index 3753206e7..a14d13219 100755 --- a/ckanext/canada/helpers.py +++ b/ckanext/canada/helpers.py @@ -482,7 +482,7 @@ def get_pd_datatable(resource_name: str, activity_priority = 1 fields = [] fids = [] - if chromo['edit_using__id']: + if chromo.get('edit_using__id'): fields.append({'type': 'int', 'id': '_id', 'label': 'open_canada_id', 'priority': 0}) fids.append('_id') for f in chromo['fields']: @@ -505,6 +505,10 @@ def get_pd_datatable(resource_name: str, fids.append(f['datastore_id']) pkids = [fids.index(k) for k in aslist(chromo['datastore_primary_key'])] + if chromo.get('edit_using__id'): + ekids = [0] + else: + ekids = pkids fkids = {} ds_info = get_action('datastore_info')( @@ -542,6 +546,7 @@ def get_pd_datatable(resource_name: str, resource_id=resource_id, owner_org=owner_org, primary_keys=pkids, + edit_keys=ekids, foreign_keys=fkids, dataset_type=dataset_type, ds_fields=fields) diff --git a/ckanext/canada/templates/snippets/pd_datatable_depr.html b/ckanext/canada/templates/snippets/pd_datatable_depr.html index 89d660ba2..144e35e08 100644 --- a/ckanext/canada/templates/snippets/pd_datatable_depr.html +++ b/ckanext/canada/templates/snippets/pd_datatable_depr.html @@ -189,10 +189,10 @@

{{_("Preview")}}

})); form.append($('').attr({ type: 'hidden', - value: {{primary_keys}}, + value: {{edit_keys}}, name: 'key_indices' })); - var pk_cols = {{primary_keys}}.map(function(value){ + var pk_cols = {{edit_keys}}.map(function(value){ return value + {{offset}}; }); let rows = dt.rows( { selected: true } ); From 56555b58b4ebd130af9a8bd42bbd7ff87d8c2154 Mon Sep 17 00:00:00 2001 From: Jesse Vickery Date: Mon, 20 Jul 2026 21:36:20 -0400 Subject: [PATCH 3/5] feat(js): pd datatable; - Updated new PD datatable code for `edit_using__id` capabilities. --- .../canada/assets/datatables/pd_datatables.js | 39 ++++++++++++++++--- .../templates/snippets/pd_datatable.html | 11 ++++++ ckanext/canada/view.py | 6 ++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ckanext/canada/assets/datatables/pd_datatables.js b/ckanext/canada/assets/datatables/pd_datatables.js index ccebbaea1..b1211bea7 100644 --- a/ckanext/canada/assets/datatables/pd_datatables.js +++ b/ckanext/canada/assets/datatables/pd_datatables.js @@ -24,6 +24,7 @@ this.ckan.module('pd-datatables', function($){ table_styles: null, primary_keys: null, foreign_keys: null, + edit_keys: null, foreign_links: null, chromo_fields: null, is_editable: false, @@ -56,6 +57,7 @@ function load_pd_datatable(CKAN_MODULE){ const ajaxURI = CKAN_MODULE.options.ajax_uri; const primaryKeys = CKAN_MODULE.options.primary_keys; const foreignKeys = CKAN_MODULE.options.foreign_keys; + const editKeys = CKAN_MODULE.options.edit_keys; const foreignLinks = CKAN_MODULE.options.foreign_links; const chromoFields = CKAN_MODULE.options.chromo_fields; const isEditable = CKAN_MODULE.options.is_editable; @@ -64,6 +66,7 @@ function load_pd_datatable(CKAN_MODULE){ // TODO: Disable Editor - enable Table Editor when ready... // const EDITOR = pd_datatables__EDITOR; const EDITOR = false; + const hasOpenCanadaID = (primaryKeys != editKeys && editKeys.length == 1 && editKeys[0] == 0); const selectAllLabel = _('Select All'); const colSearchLabel = _('Search:'); @@ -204,7 +207,10 @@ function load_pd_datatable(CKAN_MODULE){ "orderable": false, "targets": 0, "render": function(_data, _type, _row, _meta){ - if( isEditMode ){ + if( isEditMode && editingRows.length > 0 ){ + if( hasOpenCanadaID ){ + return _data; + } return _meta.row + 1; } return _data; @@ -408,7 +414,8 @@ function load_pd_datatable(CKAN_MODULE){ let readOnlyClass = ''; let tabIndex = 0; let isPrimaryKey = primaryKeys.includes(_colIndex); - if( editingRows.length > 0 && primaryKeys.includes(_colIndex) ){ + let isEditingKey = editKeys.includes(_colIndex); + if( editingRows.length > 0 && primaryKeys.includes(_colIndex) && ! hasOpenCanadaID ){ readOnly = 'readonly'; readOnlyClass = 'editor-input-readonly'; tabIndex = -1; @@ -574,13 +581,33 @@ function load_pd_datatable(CKAN_MODULE){ // Compile available columns let _sortOrderIndex = 1; + if( hasOpenCanadaID ){ + // render the open_canada_id (_id) field, and treat as primary key field + let _openCanadaIdField = { + 'type': 'int', + 'name': '_id', + 'id': '_id', + 'datastore_id': '_id', + 'label': 'open_canada_id', + 'priority': 0, + } + availableColumns.push({ + "name": '_id', + "className": ' pd-datatables-non-editable-col pd-datatables-primary-key-fixed col-sm ', + "searchable": true, + "render": function(_data, _type, _row, _meta){ + return cell_renderer(_data, _type, _row, _meta, _openCanadaIdField); + } + }); + _sortOrderIndex += 1; + } for( let i = 0; i < chromoFields.length; i++ ){ if( typeof chromoFields[i].published_resource_computed_field == 'undefined' || ! chromoFields[i].published_resource_computed_field ){ let previewClass = ''; if( typeof chromoFields[i].preview_class != 'undefined' ){ previewClass = chromoFields[i].preview_class; } - if( primaryKeys.includes(i) ){ + if( !hasOpenCanadaID && primaryKeys.includes(i) ){ previewClass += ' pd-datatables-primary-key-fixed '; } if( typeof chromoFields[i].import_template_include != 'undefined' && ! chromoFields[i].import_template_include ){ @@ -1270,14 +1297,14 @@ function load_pd_datatable(CKAN_MODULE){ enabled: ! isExportingExcel, className: "pd-datatable-btn pd-datatable-excel-btn btn-primary", action: function(e, dt, button, config){ - let pk_cols = primaryKeys.map(function(value){ + let pk_cols = editKeys.map(function(value){ return value + colOffset; }); let rows = dt.rows({ selected: true }); let params = new Object(); params['resource_name'] = resourceName; params[csrfTokenName] = csrfTokenValue; - params['key_indices'] = primaryKeys; + params['key_indices'] = editKeys; params['bulk-template'] = []; rows.eq(0).each(function(index){ params['bulk-template'].push(dt.cells(index, pk_cols).data().toArray()); @@ -1322,7 +1349,7 @@ function load_pd_datatable(CKAN_MODULE){ text: ' ' + editSingleButtonLabel, className: "pd-datatable-btn pd-datatable-btn-single btn-success", action: function(e, dt, button, config){ - let pk_cols = primaryKeys.map(function(value){ + let pk_cols = editKeys.map(function(value){ return value + colOffset; }); let rows = dt.rows({ selected: true }); diff --git a/ckanext/canada/templates/snippets/pd_datatable.html b/ckanext/canada/templates/snippets/pd_datatable.html index 080e41808..3f65d1b8e 100644 --- a/ckanext/canada/templates/snippets/pd_datatable.html +++ b/ckanext/canada/templates/snippets/pd_datatable.html @@ -117,6 +117,16 @@

{{_("Interactive Table (beta version)")}}