From 4ca952455581a1704cda27304602a95312ba0546 Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:02:08 -0700 Subject: [PATCH 01/11] Add a way to explicity choose aggregators in QB Fixes #4620 --- specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx index 8964345844e..11ac98d0507 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx @@ -167,7 +167,7 @@ export function QueryLine({ canOpenMap = fieldName === 'latitude1' || fieldName === 'longitude1'; } else if (isMapped) fieldType = - dataModelField?.isRelationship && relationshipIsToMany(dataModelField) + isFormatted && mappingPath.at(-1) === '#1' ? 'aggregator' : 'formatter'; From 4c02bb61bf2fc0f8abc3a387578243637cace5ea Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:07:51 -0700 Subject: [PATCH 02/11] Remove import --- specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx index 11ac98d0507..18f1dd92d3b 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx @@ -29,7 +29,6 @@ import { formattedEntry, mappingPathToString, parsePartialField, - relationshipIsToMany, valueIsPartialField, } from '../WbPlanView/mappingHelpers'; import { generateMappingPathPreview } from '../WbPlanView/mappingPreview'; From 4328e56dc4e9f36b423a87acdbd4548b4e886e7f Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 15 Mar 2024 12:00:45 -0500 Subject: [PATCH 03/11] edit logic for getting formatter --- specifyweb/stored_queries/format.py | 36 +++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/specifyweb/stored_queries/format.py b/specifyweb/stored_queries/format.py index 2364fcb7cb8..8a03e9b0f25 100644 --- a/specifyweb/stored_queries/format.py +++ b/specifyweb/stored_queries/format.py @@ -71,14 +71,34 @@ def getFormatterFromSchema() -> Element: or getFormatterFromSchema() \ or lookup('class', specify_model.classname) + def getFormatterDefBasic(self, specify_model: Table, formatter_name) -> Optional[Element]: + elements = self.formattersDom.findall('format[@name=%s]' % quoteattr(formatter_name)) + for element in elements: + if element.get('class') == specify_model.classname: + return element + return None + + def hasFormatterDef(self, specify_model: Table, formatter_name) -> bool: + elements = self.formattersDom.findall('format[@name=%s]' % quoteattr(formatter_name)) + for element in elements: + if element.get('class') == specify_model.classname: + return True + return False + def getAggregatorDef(self, specify_model: Table, aggregator_name) -> \ Optional[Element]: def lookup(attr: str, val: str) -> Optional[Element]: - return self.formattersDom.find( - 'aggregators/aggregator[@%s=%s]' % (attr, quoteattr(val))) + return self.formattersDom.find('aggregators/aggregator[@%s=%s]' % (attr, quoteattr(val))) + + def lookup_default(attr: str, val: str) -> Optional[Element]: + elements = self.formattersDom.findall('aggregators/aggregator[@%s=%s]' % (attr, quoteattr(val))) + for element in elements: + if element.get('default') == 'true': + return element + return None return (aggregator_name and lookup('name', aggregator_name)) \ - or lookup('class', specify_model.classname) + or lookup_default('class', specify_model.classname) def catalog_number_is_numeric(self): return self.collection.catalognumformatname == 'CatalogNumberNumeric' @@ -192,13 +212,13 @@ def case_value_convert(value): return value == 'true' if switch_field_spec.get_f def aggregate(self, query: QueryConstruct, field: Union[Field, Relationship], rel_table: SQLTable, - aggregator_name, + aggregator_formatter_name, cycle_detector=[]) -> Label: logger.info('aggregating field %s on %s using %s', field, rel_table, - aggregator_name) + aggregator_formatter_name) specify_model = datamodel.get_table(field.relatedModelName, strict=True) - aggregatorNode = self.getAggregatorDef(specify_model, aggregator_name) + aggregatorNode = self.getAggregatorDef(specify_model, aggregator_formatter_name) cycle_with_self = [*cycle_detector, (field.relatedModelName, 'aggregating')] if ( cycle_detector is not None) else None if aggregatorNode is None: @@ -206,7 +226,9 @@ def aggregate(self, query: QueryConstruct, return literal(_text("")) logger.debug("using aggregator: %s", ElementTree.tostring(aggregatorNode)) - formatter_name = aggregatorNode.attrib.get('format', None) + formatter_name = aggregator_formatter_name + if not self.hasFormatterDef(specify_model, aggregator_formatter_name): + formatter_name = aggregatorNode.attrib.get('format', None) separator = aggregatorNode.attrib.get('separator', ',') order_by = aggregatorNode.attrib.get('orderfieldname', '') limit = aggregatorNode.attrib.get('count', '') From ccb6cb4cd710b8235bddcd074095f5696e13f0aa Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 15 Mar 2024 13:52:31 -0500 Subject: [PATCH 04/11] better lookup func --- specifyweb/stored_queries/format.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/specifyweb/stored_queries/format.py b/specifyweb/stored_queries/format.py index 8a03e9b0f25..5a254c1cf96 100644 --- a/specifyweb/stored_queries/format.py +++ b/specifyweb/stored_queries/format.py @@ -54,6 +54,13 @@ def getFormatterDef(self, specify_model: Table, formatter_name) -> Optional[ def lookup(attr: str, val: str) -> Optional[Element]: return self.formattersDom.find( 'format[@%s=%s]' % (attr, quoteattr(val))) + + def lookup_name(name: str) -> Optional[Element]: + elements = self.formattersDom.findall('format[@name=%s]' % quoteattr(name)) + for element in elements: + if element.get('class') == specify_model.classname: + return element + return None def getFormatterFromSchema() -> Element: try: @@ -65,19 +72,12 @@ def getFormatterFromSchema() -> Element: except Splocalecontainer.DoesNotExist: return None - return formatter_name and lookup('name', formatter_name) + return formatter_name and lookup_name(formatter_name) - return (formatter_name and lookup('name', formatter_name)) \ + return (formatter_name and lookup_name(formatter_name)) \ or getFormatterFromSchema() \ or lookup('class', specify_model.classname) - def getFormatterDefBasic(self, specify_model: Table, formatter_name) -> Optional[Element]: - elements = self.formattersDom.findall('format[@name=%s]' % quoteattr(formatter_name)) - for element in elements: - if element.get('class') == specify_model.classname: - return element - return None - def hasFormatterDef(self, specify_model: Table, formatter_name) -> bool: elements = self.formattersDom.findall('format[@name=%s]' % quoteattr(formatter_name)) for element in elements: From fad3cc5a0ebfd45efce1c0628ff7ffdd392f99bd Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 15 Mar 2024 15:11:43 -0500 Subject: [PATCH 05/11] handle nulls in hasFormatterDef --- specifyweb/stored_queries/format.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specifyweb/stored_queries/format.py b/specifyweb/stored_queries/format.py index 5a254c1cf96..eaad5fb74d5 100644 --- a/specifyweb/stored_queries/format.py +++ b/specifyweb/stored_queries/format.py @@ -79,6 +79,8 @@ def getFormatterFromSchema() -> Element: or lookup('class', specify_model.classname) def hasFormatterDef(self, specify_model: Table, formatter_name) -> bool: + if formatter_name is None: + return False elements = self.formattersDom.findall('format[@name=%s]' % quoteattr(formatter_name)) for element in elements: if element.get('class') == specify_model.classname: From 1739c2550b665b4768d33499c99f8249749a8aa4 Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:40:12 -0700 Subject: [PATCH 06/11] Use referenceSymbol --- specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx index 18f1dd92d3b..5a4538a4d8f 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Line.tsx @@ -166,7 +166,7 @@ export function QueryLine({ canOpenMap = fieldName === 'latitude1' || fieldName === 'longitude1'; } else if (isMapped) fieldType = - isFormatted && mappingPath.at(-1) === '#1' + isFormatted && mappingPath.at(-1) === `${schema.referenceSymbol}1` ? 'aggregator' : 'formatter'; From 0dd8265b280650fe00c17288bc4752fb6efdc128 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 7 May 2024 11:12:26 -0500 Subject: [PATCH 07/11] add lookup_default function --- specifyweb/stored_queries/format.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/specifyweb/stored_queries/format.py b/specifyweb/stored_queries/format.py index eaad5fb74d5..ed4fcfcd176 100644 --- a/specifyweb/stored_queries/format.py +++ b/specifyweb/stored_queries/format.py @@ -54,6 +54,13 @@ def getFormatterDef(self, specify_model: Table, formatter_name) -> Optional[ def lookup(attr: str, val: str) -> Optional[Element]: return self.formattersDom.find( 'format[@%s=%s]' % (attr, quoteattr(val))) + + def lookup_default(attr: str, val: str) -> Optional[Element]: + elements = self.formattersDom.findall('format[@%s=%s]' % (attr, quoteattr(val))) + for element in elements: + if element.get('default') == 'true': + return element + return None def lookup_name(name: str) -> Optional[Element]: elements = self.formattersDom.findall('format[@name=%s]' % quoteattr(name)) @@ -75,6 +82,7 @@ def getFormatterFromSchema() -> Element: return formatter_name and lookup_name(formatter_name) return (formatter_name and lookup_name(formatter_name)) \ + or lookup_default('class', specify_model.classname) \ or getFormatterFromSchema() \ or lookup('class', specify_model.classname) @@ -87,8 +95,7 @@ def hasFormatterDef(self, specify_model: Table, formatter_name) -> bool: return True return False - def getAggregatorDef(self, specify_model: Table, aggregator_name) -> \ - Optional[Element]: + def getAggregatorDef(self, specify_model: Table, aggregator_name) -> Optional[Element]: def lookup(attr: str, val: str) -> Optional[Element]: return self.formattersDom.find('aggregators/aggregator[@%s=%s]' % (attr, quoteattr(val))) From a80c7daf8e69f8a82b8cd786d20de1a82cb4e2e3 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 7 May 2024 11:14:52 -0500 Subject: [PATCH 08/11] format def getFormatterDef --- specifyweb/stored_queries/format.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specifyweb/stored_queries/format.py b/specifyweb/stored_queries/format.py index ed4fcfcd176..9384400f34d 100644 --- a/specifyweb/stored_queries/format.py +++ b/specifyweb/stored_queries/format.py @@ -49,8 +49,7 @@ def __init__(self, collection, user, replace_nulls): self.replace_nulls = replace_nulls self.aggregator_count = 0 - def getFormatterDef(self, specify_model: Table, formatter_name) -> Optional[ - Element]: + def getFormatterDef(self, specify_model: Table, formatter_name) -> Optional[Element]: def lookup(attr: str, val: str) -> Optional[Element]: return self.formattersDom.find( 'format[@%s=%s]' % (attr, quoteattr(val))) From 3a5ac434584e36f7caa1f426720132c606059c3a Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Tue, 7 May 2024 17:26:17 +0000 Subject: [PATCH 09/11] Lint code with ESLint and Prettier Triggered by 2792f614a8e15f2916ad719041f3b26b334f263b on branch refs/heads/issue-4620 --- specifyweb/frontend/js_src/lib/components/FormEditor/Create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/FormEditor/Create.tsx b/specifyweb/frontend/js_src/lib/components/FormEditor/Create.tsx index af38578b3c6..67621064cbd 100644 --- a/specifyweb/frontend/js_src/lib/components/FormEditor/Create.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormEditor/Create.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { useOutletContext } from 'react-router'; import { useNavigate } from 'react-router-dom'; import type { LocalizedString } from 'typesafe-i18n'; -import { useAsyncState } from '../../hooks/useAsyncState'; +import { useAsyncState } from '../../hooks/useAsyncState'; import { useBooleanState } from '../../hooks/useBooleanState'; import { useId } from '../../hooks/useId'; import { commonText } from '../../localization/common'; From f082bc53f7285b9e978b35a2f2b1939848bd08b3 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 7 May 2024 17:32:35 -0500 Subject: [PATCH 10/11] fix python __bool__ truthiness with Element type --- specifyweb/stored_queries/format.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/specifyweb/stored_queries/format.py b/specifyweb/stored_queries/format.py index 0da39acc43d..ae9b0953b1a 100644 --- a/specifyweb/stored_queries/format.py +++ b/specifyweb/stored_queries/format.py @@ -80,10 +80,19 @@ def getFormatterFromSchema() -> Element: return formatter_name and lookup_name(formatter_name) - return (formatter_name and lookup_name(formatter_name)) \ - or lookup_default('class', specify_model.classname) \ - or getFormatterFromSchema() \ - or lookup('class', specify_model.classname) + result = formatter_name and lookup_name(formatter_name) + if result is not None: + return result + + result = lookup_default('class', specify_model.classname) + if result is not None: + return result + + result = getFormatterFromSchema() + if result is not None: + return result + + return lookup('class', specify_model.classname) def hasFormatterDef(self, specify_model: Table, formatter_name) -> bool: if formatter_name is None: @@ -105,8 +114,8 @@ def lookup_default(attr: str, val: str) -> Optional[Element]: return element return None - return (aggregator_name and lookup('name', aggregator_name)) \ - or lookup_default('class', specify_model.classname) + result = aggregator_name and lookup('name', aggregator_name) + return result if result is not None else lookup_default('class', specify_model.classname) def catalog_number_is_numeric(self): return self.collection.catalognumformatname == 'CatalogNumberNumeric' From d4a3d61c9a72acef9d8045e1c1ce81adc472238a Mon Sep 17 00:00:00 2001 From: alec_dev Date: Wed, 8 May 2024 09:58:24 -0500 Subject: [PATCH 11/11] fix return logic of getFormatterDef --- specifyweb/stored_queries/format.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/specifyweb/stored_queries/format.py b/specifyweb/stored_queries/format.py index ae9b0953b1a..ea0a8ce6718 100644 --- a/specifyweb/stored_queries/format.py +++ b/specifyweb/stored_queries/format.py @@ -78,9 +78,15 @@ def getFormatterFromSchema() -> Element: except Splocalecontainer.DoesNotExist: return None - return formatter_name and lookup_name(formatter_name) + if formatter_name: + return lookup_name(formatter_name) + else: + return None + + result = None + if formatter_name: + result = lookup_name(formatter_name) - result = formatter_name and lookup_name(formatter_name) if result is not None: return result @@ -114,7 +120,9 @@ def lookup_default(attr: str, val: str) -> Optional[Element]: return element return None - result = aggregator_name and lookup('name', aggregator_name) + result = None + if aggregator_name: + result = lookup('name', aggregator_name) return result if result is not None else lookup_default('class', specify_model.classname) def catalog_number_is_numeric(self):