Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
25612c2
feat(webportal): add export tool
grantfitzsimmons Apr 17, 2026
ee80b32
feat(webportal): add tests
grantfitzsimmons Apr 17, 2026
93bacdb
feat(webportal): split into separate file
grantfitzsimmons Apr 17, 2026
7969550
feat(webportal): build geoc values
grantfitzsimmons Apr 18, 2026
f60e0d1
fix(webportal): build proper metadata
grantfitzsimmons Apr 18, 2026
f3e181a
fix(webportal): remove unnecessary +1
grantfitzsimmons Apr 18, 2026
0a83119
chore(webportal): improve naming
grantfitzsimmons Apr 18, 2026
a2bbdf8
fix(webportal): hide image column
grantfitzsimmons Apr 18, 2026
c204bd0
fix(webportal): use title instead of filename
grantfitzsimmons Apr 18, 2026
8fad6e4
fix(webportal): fix missing import
grantfitzsimmons Apr 18, 2026
5642fe4
Merge branch 'main' into issue-7606
grantfitzsimmons Apr 21, 2026
158b0bc
Merge branch 'main' into issue-7606
grantfitzsimmons Apr 22, 2026
8d16a3b
Merge branch 'main' into issue-7606
acwhite211 Apr 22, 2026
f8ea2d9
Fix stored query parsing for nested formatted relations
acwhite211 Apr 22, 2026
82e26b1
feat(webportal): use target image info fields
grantfitzsimmons Apr 23, 2026
e05f02d
fix(webportal): remove name field from tree ranks
grantfitzsimmons Apr 23, 2026
356cd9b
fix(webportal): show only public attachments
grantfitzsimmons Apr 23, 2026
22e4cc0
feat(webportal): prevent duplicate records in export
grantfitzsimmons Apr 23, 2026
540a55d
Lint code with ESLint and Prettier
grantfitzsimmons Apr 23, 2026
4c29e59
feat: handle export failures with notifications
grantfitzsimmons Apr 27, 2026
558cf4b
fix(queries): move fields parsing into try block
grantfitzsimmons Apr 27, 2026
7141458
fix(queries): add error for unexpected export type
grantfitzsimmons Apr 27, 2026
14d4711
fix(webportal): hide traceback in prod
grantfitzsimmons Apr 27, 2026
5c6459f
fix(queries): include exception context in prod
grantfitzsimmons Apr 27, 2026
7956b26
fix: check permissions after parsing query
grantfitzsimmons Apr 27, 2026
87c70a5
fix(webportal): remove unneeded fields
grantfitzsimmons Apr 27, 2026
f742c10
Merge branch 'main' into issue-7606
grantfitzsimmons Apr 27, 2026
51730a4
fix(queries): re-raise error
grantfitzsimmons Apr 27, 2026
bad19f6
Merge branch 'main' into issue-7606
grantfitzsimmons May 11, 2026
ee9a9a3
fix: trim BigDecimal fields in WebPortal exports and speed up query p…
melton-jason May 28, 2026
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
153 changes: 91 additions & 62 deletions specifyweb/backend/inheritance/api.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,92 @@
from typing import Any, Callable

from collections.abc import Generator

from specifyweb.backend.inheritance.utils import get_cat_num_inheritance_setting, get_parent_cat_num_inheritance_setting
from specifyweb.specify.models import Collectionobject, Collectionobjectgroupjoin, Component

def parent_inheritance_post_query_processing(query, tableid, field_specs, collection, user):
if tableid == 1029 and 'catalogNumber' in [fs.fieldspec.join_path[0].name for fs in field_specs if fs.fieldspec.join_path]:
if not get_parent_cat_num_inheritance_setting(collection, user):
return list(query)

# Get the catalogNumber field index
catalog_number_field_index = [fs.fieldspec.join_path[0].name for fs in field_specs if fs.fieldspec.join_path].index('catalogNumber') + 1

# op_num 1 is refering to the filter equal, the inheritance will only work if we have cat num equal, other operators will not function
if field_specs[catalog_number_field_index - 1].op_num != 1:
return list(query)

results = list(query)
updated_results = []

# Map results, replacing null catalog numbers with the parent catalog number
for result in results:
result = list(result)
if result[catalog_number_field_index] is None or result[catalog_number_field_index] == '':
component_id = result[0] # Assuming the first column is the child's ID
component_obj = Component.objects.filter(id=component_id).first()
if component_obj and component_obj.collectionobject:
result[catalog_number_field_index] = component_obj.collectionobject.catalognumber
updated_results.append(tuple(result))

return updated_results

return query

def cog_inheritance_post_query_processing(query, tableid, field_specs, collection, user):
if tableid == 1 and 'catalogNumber' in [fs.fieldspec.join_path[0].name for fs in field_specs if fs.fieldspec.join_path]:
if not get_cat_num_inheritance_setting(collection, user):
# query = query.filter(collectionobjectgroupjoin_1.isprimary == 1)
return list(query)

# Get the catalogNumber field index
catalog_number_field_index = [fs.fieldspec.join_path[0].name for fs in field_specs if fs.fieldspec.join_path].index('catalogNumber') + 1

# op_num 1 is refering to the filter equal, the inheritance will only work if we have cat num equal, other operators will not function
if field_specs[catalog_number_field_index - 1].op_num != 1:
return list(query)

results = list(query)
updated_results = []

# Map results, replacing null catalog numbers with the collection object group primary collection catalog number
for result in results:
result = list(result)
if result[catalog_number_field_index] is None or result[catalog_number_field_index] == '':
cojo = Collectionobjectgroupjoin.objects.filter(childco_id=result[0]).first()
if cojo:
primary_cojo = Collectionobjectgroupjoin.objects.filter(
parentcog=cojo.parentcog, isprimary=True).first()
if primary_cojo:
result[catalog_number_field_index] = primary_cojo.childco.catalognumber
updated_results.append(tuple(result))

return updated_results

return query
from specifyweb.specify.models import Collectionobjectgroupjoin, Component


def do_nothing[T](items: T) -> T:
return items


def parent_inheritance_query_processor(tableid, field_specs, collection, user) -> Callable[[list], list]:
first_field_names = [fs.fieldspec.join_path[0].name for fs in field_specs if fs.fieldspec.join_path]
if tableid != 1029 or 'catalogNumber' not in first_field_names:
return do_nothing

if not get_parent_cat_num_inheritance_setting(collection, user):
return do_nothing

# Get the catalogNumber field index
catalog_number_field_index = first_field_names.index('catalogNumber') + 1

# op_num 1 is refering to the filter equal, the inheritance will only work if we have cat num equal, other operators will not function
if field_specs[catalog_number_field_index - 1].op_num != 1:
return do_nothing

def _processor(row: list):
modified_row = list(row)
if modified_row[catalog_number_field_index] is None or modified_row[catalog_number_field_index] == '':
component_id = row[0]
# REFACTOR:(perf) This is relatively pretty slow, we make a
# database query for every item in the iterable we're processing
component_obj = Component.objects.filter(
id=component_id).select_related("collectionobject").first()
if component_obj and component_obj.collectionobject:
modified_row[catalog_number_field_index] = component_obj.collectionobject.catalognumber

return modified_row

return _processor


def cog_inheritance_query_processor(tableid, field_specs, collection, user) -> Callable[[list], list]:
first_field_names = [fs.fieldspec.join_path[0].name for fs in field_specs if fs.fieldspec.join_path]
if tableid != 1 and 'catalogNumber' not in first_field_names:
return do_nothing

if not get_cat_num_inheritance_setting(collection, user):
return do_nothing

# Get the catalogNumber field index
catalog_number_field_index = first_field_names.index('catalogNumber') + 1

# op_num 1 is refering to the filter equal, the inheritance will only work if we have cat num equal, other operators will not function
if field_specs[catalog_number_field_index - 1].op_num != 1:
return do_nothing

# For a given result, replace null catalog numbers with the collection
# object group primary collection catalog number
def _processor(row: list):
modified_row = list(row)
if modified_row[catalog_number_field_index] is None or modified_row[catalog_number_field_index] == '':
child_co_id = modified_row[0]
# REFACTOR:(perf) We should be able to combine the queries for cojo
# and primary cojo to make one explict database query rather than
# two.
# Like parent_inheritance_query_processor, this is slow and
# makes multiple queries for each item that needs processed
cojo = Collectionobjectgroupjoin.objects.filter(
childco_id=child_co_id).select_related("parentcog").first()
if cojo:
primary_cojo = Collectionobjectgroupjoin.objects.filter(
parentcog=cojo.parentcog, isprimary=True).select_related("childco").first()
if primary_cojo:
modified_row[catalog_number_field_index] = primary_cojo.childco.catalognumber
return modified_row

return _processor


def DefaultQueryProcessors(tableid, field_specs, collection, user) -> list[Callable[[list], list]]:
kwargs = {
"tableid": tableid,
"field_specs": field_specs,
"collection": collection,
"user": user
}
return [
parent_inheritance_query_processor(**kwargs),
cog_inheritance_query_processor(**kwargs)
]
Loading
Loading