Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
formattedEntry,
mappingPathToString,
parsePartialField,
relationshipIsToMany,
valueIsPartialField,
} from '../WbPlanView/mappingHelpers';
import { generateMappingPathPreview } from '../WbPlanView/mappingPreview';
Expand Down Expand Up @@ -167,7 +166,7 @@ export function QueryLine({
canOpenMap = fieldName === 'latitude1' || fieldName === 'longitude1';
} else if (isMapped)
fieldType =
dataModelField?.isRelationship && relationshipIsToMany(dataModelField)
isFormatted && mappingPath.at(-1) === `${schema.referenceSymbol}1`
? 'aggregator'
: 'formatter';

Expand Down
79 changes: 63 additions & 16 deletions specifyweb/stored_queries/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,25 @@ 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)))

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))
for element in elements:
if element.get('class') == specify_model.classname:
return element
return None

def getFormatterFromSchema() -> Element:
try:
formatter_name = Splocalecontainer.objects.get(
Expand All @@ -65,20 +78,52 @@ 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)

if result is not None:
return result

return (formatter_name and lookup('name', formatter_name)) \
or getFormatterFromSchema() \
or lookup('class', specify_model.classname)
result = lookup_default('class', specify_model.classname)
if result is not None:
return result

def getAggregatorDef(self, specify_model: Table, aggregator_name) -> \
Optional[Element]:
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:
return False
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)
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):
return self.collection.catalognumformatname == 'CatalogNumberNumeric'
Expand Down Expand Up @@ -201,21 +246,23 @@ 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:
logger.warning("aggregator is not defined")
return literal(_text("<Aggregator not defined.>"))
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', '')
Expand Down