From 7dfbd164b92b4d67b6319095796becaa51ba44cc Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Tue, 23 Jul 2024 15:23:20 +0800 Subject: [PATCH 01/10] Add validation for parent sample --- .../ckanext/igsn_theme/logic/validators.py | 131 +++++++++++------- .../igsn_theme/sample_repository_schema.yaml | 1 + 2 files changed, 85 insertions(+), 47 deletions(-) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py index d0b02f4b..23bd5bb7 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py @@ -23,19 +23,20 @@ StopOnError = df.StopOnError not_empty = get_validator('not_empty') - +missing_error = _("Missing value") +invalid_error = _("Invalid value") # A dictionary to store your validators all_validators = {} +def add_error(errors, key, error_message): + errors[key] = errors.get(key, []) + errors[key].append(error_message) @scheming_validator @register_validator def location_validator(field, schema): def validator(key, data, errors, context): - missing_error = _("Missing value") - invalid_error = _("Invalid value") - location_choice_key = ('location_choice',) location_data_key = ('location_data',) epsg_code_key = ('epsg_code',) @@ -46,10 +47,6 @@ def validator(key, data, errors, context): epsg_code = data.get(epsg_code_key, missing) elevation = data.get(elevation_key, missing) - def add_error(key, error_message): - errors[key] = errors.get(key, []) - errors[key].append(error_message) - # Exit the validation for noLocation choice if location_choice == 'noLocation': for key in [location_data_key]: @@ -61,16 +58,16 @@ def add_error(key, error_message): try: location_data = json.loads(location_data) except ValueError: - add_error(location_data_key, invalid_error) + add_error(errors,location_data_key, invalid_error) return elif not isinstance(location_data, dict): - add_error(location_data_key, invalid_error) + add_error(errors,location_data_key, invalid_error) return features = location_data.get('features', []) if not features: - add_error(location_data_key, missing_error) + add_error(errors,location_data_key, missing_error) return if location_choice == 'point': @@ -78,7 +75,7 @@ def add_error(key, error_message): if feature['geometry']['type'] == 'Point': coords = feature['geometry']['coordinates'] if not is_valid_longitude(coords[0]) or not is_valid_latitude(coords[1]): - add_error(location_data_key, invalid_error) + add_error(errors,location_data_key, invalid_error) break elif location_choice == 'area': @@ -87,23 +84,23 @@ def add_error(key, error_message): for polygon in feature['geometry']['coordinates']: for coords in polygon: if not is_valid_longitude(coords[0]) or not is_valid_latitude(coords[1]): - add_error(location_data_key, invalid_error) + add_error(errors,location_data_key, invalid_error) return else: - add_error(location_data_key, missing_error) + add_error(errors, location_data_key, missing_error) if location_choice is missing and field.get('required', False): - add_error(location_choice_key, missing_error) + add_error(errors, location_choice_key, missing_error) if epsg_code is missing: - add_error(epsg_code_key, missing_error) + add_error(errors, epsg_code_key, missing_error) if elevation is not missing and elevation is not None and str(elevation).strip(): try: elevation = float(elevation) except (ValueError, TypeError): - add_error(elevation_key, invalid_error) + add_error(errors, elevation_key, invalid_error) log = logging.getLogger(__name__) try: @@ -122,7 +119,7 @@ def add_error(key, error_message): except Exception as e: log.error("Error processing GeoJSON: %s", e) - add_error(location_data_key, f"Error processing GeoJSON: {e}") + add_error(errors, location_data_key, f"Error processing GeoJSON: {e}") return validator @@ -362,12 +359,6 @@ def owner_org_validator(key, data, errors, context): @register_validator def sample_number_validator(field, schema): def validator(key, data, errors, context): - missing_error = _("Missing value") - invalid_error = _("Invalid value") - - def add_error(key, error_message): - errors[key] = errors.get(key, []) - errors[key].append(error_message) sample_number = data.get(key) owner_org_key = ('owner_org',) @@ -375,11 +366,11 @@ def add_error(key, error_message): current_sample_id = data.get(('id',), None) if owner_org is missing: - add_error(owner_org_key, missing_error) + add_error(errors, owner_org_key, missing_error) return if sample_number is missing or sample_number == '': - add_error(key, missing_error) + add_error(errors, key, missing_error) return try: @@ -393,12 +384,12 @@ def add_error(key, error_message): for result in search_result['results']: if result['id'] != current_sample_id: org_name = tk.get_action('organization_show')({}, {'id': owner_org})['name'] - add_error(key, f'sample_number "{sample_number}" already exists in collection "{org_name}"') + add_error(errors, key, f'sample_number "{sample_number}" already exists in collection "{org_name}"') break # Stop checking after the first duplicate is found except NotFound: - add_error(key, 'Error checking uniqueness of sample_number') + add_error(errors, key, 'Error checking uniqueness of sample_number') except Exception as e: - add_error(key, f'Error querying Solr: {str(e)}') + add_error(errors, key, f'Error querying Solr: {str(e)}') return @@ -415,10 +406,6 @@ def acquisition_date_validator(field, schema): """ def validator(key, data, errors, context): - def add_error(key, error_message): - errors[key] = errors.get(key, []) - errors[key].append(error_message) - acquisition_start_date_key = ('acquisition_start_date',) acquisition_end_date_key = ('acquisition_end_date',) @@ -433,21 +420,21 @@ def add_error(key, error_message): try: acquisition_start_date = datetime.strptime(acquisition_start_date_str, "%Y-%m-%d").date() except ValueError: - add_error(acquisition_start_date_key, 'Invalid date format. Please use YYYY-MM-DD.') + add_error(errors, acquisition_start_date_key, 'Invalid date format. Please use YYYY-MM-DD.') return try: acquisition_end_date = datetime.strptime(acquisition_end_date_str, "%Y-%m-%d").date() except ValueError: - add_error(acquisition_end_date_key, 'Invalid date format. Please use YYYY-MM-DD.') + add_error(errors, acquisition_end_date_key, 'Invalid date format. Please use YYYY-MM-DD.') return if acquisition_start_date > datetime.now().date(): - add_error(acquisition_start_date_key, 'Acquisition start date must be today or before.') + add_error(errors, acquisition_start_date_key, 'Acquisition start date must be today or before.') return if acquisition_start_date > acquisition_end_date: - add_error(acquisition_end_date_key, 'Acquisition end date must be later than the start date.') + add_error(errors, acquisition_end_date_key, 'Acquisition end date must be later than the start date.') return return validator @@ -459,12 +446,6 @@ def depth_validator(field, schema): A validator to ensure the depth_from is less than depth_to """ def validator(key, data, errors, context): - missing_error = _("Missing value") - invalid_error = _("Invalid value") - - def add_error(key, error_message): - errors[key] = errors.get(key, []) - errors[key].append(error_message) depth_from_key = ('depth_from',) depth_to_key = ('depth_to',) @@ -478,17 +459,72 @@ def add_error(key, error_message): try: depth_from = float(depth_from_str) except (ValueError, TypeError): - add_error(depth_from_key, invalid_error) + add_error(errors, depth_from_key, invalid_error) return try: depth_to = float(depth_to_str) except (ValueError, TypeError): - add_error(depth_to_key, invalid_error) + add_error(errors, depth_to_key, invalid_error) return if depth_from > depth_to: - add_error(depth_to_key, _("Depth to must be greater than the depth from.")) + add_error(errors, depth_to_key, _("Depth to must be greater than the depth from.")) + + return validator + +@scheming_validator +@register_validator +def parent_validator(field, schema): + """ + A validator to ensure that if the parent sample is specified, + then the acquisition start date of the sample must be either the same as or later than the acquisition start date of its parent sample. + Additionally, the sample and its parent must belong to the same organization and cannot be the same. + """ + def validator(key, data, errors, context): + + parent_sample_id_key = ('parent',) + parent_sample_id = data.get(parent_sample_id_key, missing) + start_date_key = ('acquisition_start_date',) + start_date = data.get(start_date_key, missing) + owner_org_key = ('owner_org',) + owner_org = data.get(owner_org_key, missing) + sample_id_key = ('id',) + sample_id = data.get(sample_id_key, missing) + + if parent_sample_id is missing or parent_sample_id is None or not str(parent_sample_id).strip(): + return + + if sample_id == parent_sample_id: + add_error(errors, parent_sample_id_key, _('A sample cannot be its own parent.')) + return + + try: + parent_sample = tk.get_action('package_show')(context, {'id': parent_sample_id}) + except tk.ObjectNotFound: + add_error(errors, parent_sample_id_key, _('Parent sample not found.')) + return + except tk.NotAuthorized: + add_error(errors, parent_sample_id_key, _('You are not authorized to view the parent sample.')) + return + + parent_owner_org = parent_sample.get('owner_org', missing) + if owner_org is missing or parent_owner_org is missing or owner_org != parent_owner_org: + add_error(errors, parent_sample_id_key, _('The sample and its parent must belong to the same organization.')) + return + + parent_start_date = parent_sample.get('acquisition_start_date', missing) + + if start_date and parent_start_date and str(start_date).strip() and str(parent_start_date).strip(): + try: + start_date_dt = datetime.strptime(start_date, "%Y-%m-%d") + parent_start_date_dt = datetime.strptime(parent_start_date, "%Y-%m-%d") + except ValueError: + add_error(errors, parent_sample_id_key, _('Invalid date format. Use YYYY-MM-DD.')) + return + + if start_date_dt < parent_start_date_dt: + add_error(errors, parent_sample_id_key, _('The Acquisition Start Date of the sample must be the same as or later than the acquisition start date of its parent sample.')) return validator @@ -500,5 +536,6 @@ def get_validators(): "owner_org_validator": owner_org_validator, "sample_number_validator" : sample_number_validator, "acquisition_date_validator" : acquisition_date_validator, - "depth_validator" : depth_validator + "depth_validator" : depth_validator, + "parent_validator" : parent_validator } diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml index a4fcdc2e..3657fc03 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml @@ -40,6 +40,7 @@ dataset_fields: label: Parent Sample form_snippet: sample_parent_field.html display_snippet: sample_parent_field.html + validators: parent_validator help_text: The source sample from which the sample is created or sub-sampled. Select the parent sample from existing samples. groupBy: About Sample From b397e1b6db3a1733c9e2fed4f02ab84637de5f2d Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Wed, 24 Jul 2024 10:56:57 +0800 Subject: [PATCH 02/10] Remove the search and entries per page features from the DataTable --- .../templates/organization/snippets/organization_list.html | 6 +++++- .../igsn_theme/templates/snippets/package_list.html | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/organization/snippets/organization_list.html b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/organization/snippets/organization_list.html index b3480c81..f3feab54 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/organization/snippets/organization_list.html +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/organization/snippets/organization_list.html @@ -31,6 +31,10 @@ diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/snippets/package_list.html b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/snippets/package_list.html index cf9bfeb0..a73765a6 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/snippets/package_list.html +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/snippets/package_list.html @@ -23,6 +23,11 @@ + From d969edde4053ad68fd57e6ee2a880f41dffb0618 Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Wed, 24 Jul 2024 11:21:04 +0800 Subject: [PATCH 03/10] fix bug, user_keywords Field Mandatory --- .../ckanext/igsn_theme/sample_repository_schema.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml index 3657fc03..13adf4c7 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_repository_schema.yaml @@ -239,6 +239,7 @@ dataset_fields: label: User Specified Keywords required: true preset: tag_string_autocomplete + validators: not_empty tag_string_convert help_text: Keywords help with the discovery of the physical sample groupBy: About Sample From 3c489906af30624a574f3ac9c212e6561f0a114d Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Mon, 29 Jul 2024 11:29:51 +0800 Subject: [PATCH 04/10] Check collection for no members before deleting and send email notification after deletion --- .../igsn_theme/i18n/en_AU/LC_MESSAGES/ckan.mo | Bin 59796 -> 59794 bytes .../igsn_theme/i18n/en_AU/LC_MESSAGES/ckan.po | 2 +- .../ckanext/igsn_theme/logic/action.py | 68 ++++++++++++++---- .../igsn_theme/logic/email_notifications.py | 20 ++++++ .../snippets/organization_form.html | 8 +++ 5 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/organization/snippets/organization_form.html diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/i18n/en_AU/LC_MESSAGES/ckan.mo b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/i18n/en_AU/LC_MESSAGES/ckan.mo index a1381eab8ee9695e3def9c3567a97e5d2c3da260..23c2a792ea7c2f4afc182d34931f32ebd8350c39 100644 GIT binary patch delta 5583 zcmXZfdwkE=9mnynCK5>`rT61xYMPW9pK&MunAjq(*9wmY}1?%)Wh4L${Px z+PZa*2GO`xD<~tQajQ$>UX)UoFblIOkGXX0_0BneJ)^f-3!x7jVi!lM; z#7O)I!*LzP;I8G4>jcrbNJj)VU=%tl948-IVKW?x!8j5R<9O6WIV&9}69=Le{uAoG zQVhXbY=P&|A8(=(yNe!dQsJ5baTSi^OGgH3!7S8-Jy9?ALrpjkTVfF^p$XU)XP|bp z1l!>@)Iz6G^IS!}-++4Wp?%)WUFA5TbVQ;Ccx{}C+F=2v<8ahYN>K@Yf?Bu&J7NvC z!Ujx4pU=#~oiLC%9hFcHs?r6hO1mRy=*7vXqxcJ^;vx*d{rEFHhN?{SO0$DVjBVmL z@fb#bpVemkFjQirtSZMMqcv(LF{m@{g1QsEFcAA; z5Eh~)E<%-bJnBrR<3OBexh0e^Bc@u+N_;W_>4kojL1p)Iwg=iy5dFa#3g2 z3$??csBuN8JMuazp&6(|N>K|gMU7v9@wf&xUma%PNpv-l|9Z32Fx0}WP?s?lHBmZh zqHJqEs^oo9XL4C*NVfExcH>iuP?O0GnWUyZ78)q3i$jy-hfZ0j%>Z{bt;2$jfV zROZ2-o1KTF;uzHP1l!*gHP17ch$B#ynT4w0LR4av7>gS|*ZI>pMn?{wL1h}T!5EFY zEHS9B#EU9Cpf@8uVsKnk!ZR}%fIkq5Pi>ml`8}D;zB+&6KhTtPRAn;4G zaHKT`HL(|iFaFOHh@W zij5PX7M^3DFGMBuSM=ga)cC`wot{8d;0kKKTi6osqY@3MYMjq?qG;&FIP}3zsFJ7H z{!9!b&a?50s0oIm5-UbkV4U^$sD-$LX~_ZDzPb;f^)F(``=7M3vWl2cn@la zM{WQ2sD!Se68SfV;}cYZ;ajL0#-bK@0reFON9}kzhGH3N-c_jis?b%&)im^1s}?mu z14iIa7>A)>nLo4LFpGFBCgKXz_yec~zC+!io3_8>R#Vv?sD*o@HZTa4;K;4iUlX}> z=!_~*ne9cD_y}s^`_{m1W`ee;36oIoXJaZBVIG#E?#3ZhWlx|k;d#`CuVWVecN^y) zO(TtuSv!6X6&In7Vk)ZC?_d-zLhX11DuG=XiM7}Uub{>~K`k7$-6Y%|wc}LO{J%gY z_#2l-0gVZ$iMFDSq7K{OO?(=g?JzIIqY_B6aW3i(^tN#!YT;LGJQ}s*v8bzLQi(dk&8Q0QL@j&>mFRJ7 zhd&^9!F3+fP)5OjH>D1@dQl7JpkC;Ws>CSN@4*agTo{vxEA8`>w*NLN5&xa$vkgVv zfhZftVwk@F4)#G0R0Vq5cnE5OS5Z579d#KeqINnHwZr+STfNjiUxPZL9k#y~b+qSE z^V~tL^8lk<8Uee^*(da)ce(U<7Kt*E~tXHor+u@g4^+Eg$N zm0&jN{eGy555>mse*_J^Sb|z;GJ4QO?f6qvVjEE}?m*pv8tZM;Pios5)8Ertf;#gh z=*3Em!=tF9{jrAn>vQ;t4lNM1+nkjLwQwS8U>53Y=!uCq9Aj}Nw#JpH1*$QpiR0Ac z0OHuaCb5awmADL*z&EJN-LRMXcc2lq&rFbqeTiQ~U8;4~Gnh{7yWd>K?wCeA7?sE@ z%*6`qf|pU_Bfc>|G<{GNn}J$*4W{BLmxh;y?*X%5GU`?jLk*mW`axNW`U*CpzJjCp zHa4IK#~m~Ym7*4|MveOyW?;Y}Q^8!!As&WJ(JiB)+q?v|@Mowq+iBxk8`q;=_z^qe z1N30rVe@DES=3+0e%Jx~V+>BR@dq~Eh93IQAeD2S2Q-vnn_Bb20BlcOf~rg@GSJzK zI=j=T9X`aim{eyfQ-FFt2Gu_gmB1?0e0$M@^%#$SM>Id@-h!JJRtK_0OX*@RXUT z6qUec)QcBUF9d#T{;$u*4C1M%gx8`6ub^(b?`c!o_LxeXiaOd6n29q`N4g1hnX6Az zfBmrRwjCEyXI+nJcnekXc4y2H#9lJ< zSK!{Dq3=2PJ9FtWQKkGPM&m?mh412cT#khpdX66vEXGQ##``$tyg9m8zc=%|i(Tli zz{bxPJ;V=@yW=`-E|^k3i#q#BsDwU2-QsDZrmF-G7u)R8(P(60d)uN zpceMMVvaBz+Y@J?`U_F-{~C3NCZq1qY;-ljQW|nSYUevqJ2-+*;W^YLy=PVK%;vvAFFj^-rO3&UOUUo9{gde@_3)n2SqM37)}t z{3mLHkZa~LrK93OsQD&gK9-}7=n^WSC+Lg8*Ud)5u2X+?#MurnD$cTTu8sSnF5w_7 zz(1lE+>iR9x`BN$;veRx_E-2K@d8Z5OQ>=FH_Tnhz^91^rql4D@dj$)@u(x1WaC-Z fxz^IEX=xd6`i-72Zrp357ryuWOH~2oe=hhxmdw7# delta 5576 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 _('Delete') }} + {% endif %} +{% endblock %} + From 643e1fedf3dd645831303373e80c5786c3ec3998 Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Mon, 29 Jul 2024 15:20:25 +0800 Subject: [PATCH 05/10] Fix group name validation to consider the statu of collection witch are deleted --- .../ckanext/igsn_theme/logic/validators.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py index 23bd5bb7..4042864a 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py @@ -4,6 +4,7 @@ import ckanext.scheming.helpers as sh import ckan.lib.navl.dictization_functions as df +from typing import Any, Union, Optional from ckanext.scheming.validation import scheming_validator, register_validator from ckan.logic import NotFound @@ -528,6 +529,39 @@ def validator(key, data, errors, context): return validator + +@scheming_validator +@register_validator +def group_name_validator(field, schema): + + def validator(key, data,errors, context): + """Ensures that value can be used as a group's name + """ + + model = context['model'] + session = context['session'] + group = context.get('group') + + query = session.query(model.Group.name).filter( + model.Group.name == data[key], + model.Group.state != model.State.DELETED + ) + + if group: + group_id: Union[Optional[str], df.Missing] = group.id + else: + group_id = data.get(key[:-1] + ('id',)) + + if group_id and group_id is not missing: + query = query.filter(model.Group.id != group_id) + + result = query.first() + if result: + add_error(errors, key, _('Collection name already exists in database.')) + + return validator + + def get_validators(): return { "igsn_theme_required": igsn_theme_required, @@ -537,5 +571,6 @@ def get_validators(): "sample_number_validator" : sample_number_validator, "acquisition_date_validator" : acquisition_date_validator, "depth_validator" : depth_validator, - "parent_validator" : parent_validator + "parent_validator" : parent_validator, + "group_name_validator" : group_name_validator } From 6311dc78d346cb82d06547c8b6c14cf333350a05 Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Mon, 29 Jul 2024 15:21:07 +0800 Subject: [PATCH 06/10] Modify collection metadata to make contact email as required --- .../ckanext/igsn_theme/sample_organization_schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_organization_schema.yaml b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_organization_schema.yaml index bbccae3f..5300dda6 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_organization_schema.yaml +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/sample_organization_schema.yaml @@ -21,7 +21,7 @@ fields: - field_name: contact_email label: Contact Email required: true - validators: ignore_missing unicode_safe strip_value email_validator + validators: not_empty unicode_safe strip_value email_validator - field_name: description label: Description From e1a1eeabf5f84d8b8f1a2a9bdf3aed64e1c8ab0b Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Mon, 29 Jul 2024 15:36:33 +0800 Subject: [PATCH 07/10] The reference should be smaller than main text #258 --- .../contact/snippets/new_collection_form.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/contact/snippets/new_collection_form.html b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/contact/snippets/new_collection_form.html index 3c616247..3caacf6d 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/contact/snippets/new_collection_form.html +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/contact/snippets/new_collection_form.html @@ -53,10 +53,12 @@ {{ errors.is_culturally_sensitive }} {% endif %} - [1] Maiam nayri Wingara. (2018) Indigenous Data Sovereignty Communique Indigenous Data Sovereignty Summit 20th June 2018, Canberra, ACT. - https://static1.squarespace.com/static/5b3043afb40b9d20411f3512/t/63ed934fe861fa061ebb9202/1676514134724/Communique-Indigenous-Data-Sovereignty-Summit.pdf - - - +
+ + [1] Maiam nayri Wingara. (2018) Indigenous Data Sovereignty Communique Indigenous Data Sovereignty Summit 20th June 2018, Canberra, ACT. + https://static1.squarespace.com/static/5b3043afb40b9d20411f3512/t/63ed934fe861fa061ebb9202/1676514134724/Communique-Indigenous-Data-Sovereignty-Summit.pdf + +
+ {% endblock %} From 186e90740e03df37223a48ac9e740eaaffbd9fbd Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Mon, 29 Jul 2024 15:41:45 +0800 Subject: [PATCH 08/10] Modify the depth validatior as depth_from < depth_to (not <=) #267 --- .../ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py index 4042864a..1d56b3c8 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/logic/validators.py @@ -469,7 +469,7 @@ def validator(key, data, errors, context): add_error(errors, depth_to_key, invalid_error) return - if depth_from > depth_to: + if depth_from >= depth_to: add_error(errors, depth_to_key, _("Depth to must be greater than the depth from.")) return validator From b2b739becfd9442c8e9b52278926e4bc209d60e6 Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Tue, 30 Jul 2024 11:21:40 +0800 Subject: [PATCH 09/10] Fix issue with elevation input field: Ensure consistent behavior across browsers #262 --- ckan/src/shared/public/base/js/map-module.js | 19 +++++++++++++++++++ .../form_snippets/spatial_fields.html | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ckan/src/shared/public/base/js/map-module.js b/ckan/src/shared/public/base/js/map-module.js index 476300d8..2a4144f5 100644 --- a/ckan/src/shared/public/base/js/map-module.js +++ b/ckan/src/shared/public/base/js/map-module.js @@ -8,6 +8,9 @@ ckan.module('map-module', function ($, _) { this.singleMode = this.options.singleMode; this.el.ready($.proxy(this.setupMap, this)); + + this.setupElevationField(); + }, setupMap: function () { @@ -547,6 +550,22 @@ ckan.module('map-module', function ($, _) { }, + setupElevationField: function () { + document.getElementById('field-elevation').addEventListener('input', function (event) { + const inputField = event.target; + const value = inputField.value; + const validValue = value.replace(/[^0-9.]/g, ''); + const parts = validValue.split('.'); + if (parts.length > 2) { + inputField.value = parts[0] + '.' + parts.slice(1).join(''); + } else { + inputField.value = validValue; + } + if (parseFloat(inputField.value) < 0) { + inputField.value = ''; + } + }); + }, }; }); diff --git a/ckan/src/shared/templates/scheming/form_snippets/spatial_fields.html b/ckan/src/shared/templates/scheming/form_snippets/spatial_fields.html index b4612b11..f381ef9e 100644 --- a/ckan/src/shared/templates/scheming/form_snippets/spatial_fields.html +++ b/ckan/src/shared/templates/scheming/form_snippets/spatial_fields.html @@ -57,7 +57,8 @@
- + + {% if errors['elevation'] %} {% for error in errors['elevation'] %} {{ error }} From 623d9f90a210cd49f0010eacc314ac1462a56eea Mon Sep 17 00:00:00 2001 From: Neda Taherifar Date: Thu, 1 Aug 2024 10:28:55 +0800 Subject: [PATCH 10/10] Fix issue where child samples are not clickable (#269) --- .../assets/js/jstree-view-module.js | 42 ++++++++++--------- .../package/snippets/sample_family_info.html | 17 ++++++++ 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/assets/js/jstree-view-module.js b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/assets/js/jstree-view-module.js index c4e4cc60..8d1af3a5 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/assets/js/jstree-view-module.js +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/assets/js/jstree-view-module.js @@ -9,7 +9,7 @@ this.ckan.module('jstree-view-module', function (jquery) { var id = this.el.attr('data-id'); var title = this.el.attr('data-title'); - var truncatedTitle = self.truncateText(title, 20); + var truncatedTitle = self.truncateText(title, 20); var data = [{ "text": truncatedTitle, @@ -17,8 +17,7 @@ this.ckan.module('jstree-view-module', function (jquery) { "state": { "opened": true }, - "a_attr": { "title": title } - + "a_attr": { "title": title} }]; $('#tree').jstree({ @@ -43,22 +42,26 @@ this.ckan.module('jstree-view-module', function (jquery) { } } }).on("select_node.jstree", function (e, data) { - if (data.node.children.length === 0) { + if (data.node.children.length === 0 && data.node.type !== "leaf") { self.fetchChildren(data.node.id); } - }); - - $('#tree').on("dblclick", ".jstree-anchor", function (e) { - var href = $(this).attr('href'); - console.log(href); - if (href) { - e.preventDefault(); - window.location.href = href; + }).on("click", ".jstree-anchor", function (e) { + e.preventDefault(); // Prevent default behavior + + var node = $('#tree').jstree(true).get_node(this.id.replace('_anchor', '')); + console.log( node) + if (node && (node.children.length > 0 || node.type === "leaf")) { + console.log("link"); + var href = $(this).attr("href"); + if (href) { + window.location.href = href; // Explicitly navigate to the link + } + } else { + e.preventDefault(); // Prevent navigation if the node is not clickable } }); - self.fetchChildren(id); - + self.fetchChildren(id); }, fetchChildren: function (packageId) { @@ -90,7 +93,7 @@ this.ckan.module('jstree-view-module', function (jquery) { var childrenCount; var checkedChildren = []; var self = this; - + function verifyChild(child) { $.ajax({ url: '/api/3/action/package_show', @@ -113,8 +116,8 @@ this.ckan.module('jstree-view-module', function (jquery) { if (--childrenCount === 0) { if (checkedChildren.length > 0) { checkedChildren.forEach(function (validChild) { - validChild.a_attr = Object.assign({}, validChild.a_attr, { "title": validChild.text }); - validChild.text = self.truncateText(validChild.text, 20); + validChild.a_attr = Object.assign({}, validChild.a_attr, { "title": validChild.text, "href": "/dataset/" + validChild.id.toLowerCase(), "class": "clickable-node" }); + validChild.text = self.truncateText(validChild.text, 20); $('#tree').jstree(true).create_node(packageId, validChild, "last"); $('#tree').jstree(true).open_node(packageId); }); @@ -132,13 +135,13 @@ this.ckan.module('jstree-view-module', function (jquery) { verifyChild({ "text": childData.object, "id": childData.object, - "a_attr": { href: "/dataset/" + childData.object.toLowerCase(), target: "_blank" } + "a_attr": { href: "/dataset/" + childData.object.toLowerCase(), "class": "clickable-node" } }); }); } }; }, - + truncateText: function (text, maxLength) { if (text.length <= maxLength) { return text; @@ -148,5 +151,4 @@ this.ckan.module('jstree-view-module', function (jquery) { return startText + '...' + endText; } } - }); diff --git a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/package/snippets/sample_family_info.html b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/package/snippets/sample_family_info.html index 6743531a..a52a6f7d 100644 --- a/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/package/snippets/sample_family_info.html +++ b/ckan/src/ckanext-igsn-theme/ckanext/igsn_theme/templates/package/snippets/sample_family_info.html @@ -65,4 +65,21 @@

Sample Family

visibility: visible; opacity: 1; } + +.clickable-node:hover { + color: var(--color-aus-royal-blue) !important; + text-decoration: underline; +} + +.jstree-ocl { + background-color: lightgray; /* Distinguishable background color */ + border-radius: 50%; /* Rounded appearance */ + padding: 2px; + display: inline-block; +} + +/* Hover style for collapse/expand icons */ +.jstree-ocl:hover { + background-color: darkgray; /* Change background on hover */ +} \ No newline at end of file