Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 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
4832a3f
Apply remaining changes
Copilot Jul 13, 2026
1bace31
Lint code with ESLint and Prettier
Copilot Jul 13, 2026
064e40b
Potential fix for pull request finding 'CodeQL / Unused import'
CarolineDenis Jul 13, 2026
76c8322
Potential fix for pull request finding 'CodeQL / Unused import'
CarolineDenis Jul 13, 2026
ef2f14c
Potential fix for pull request finding 'CodeQL / Unused import'
CarolineDenis Jul 13, 2026
8502305
Potential fix for pull request finding 'CodeQL / Unused import'
CarolineDenis Jul 13, 2026
7e43c83
Potential fix for pull request finding 'CodeQL / Unused import'
CarolineDenis Jul 13, 2026
d5d04b9
Potential fix for pull request finding 'CodeQL / Unused import'
CarolineDenis Jul 13, 2026
7228455
Merge branch 'main' into issue-7606
grantfitzsimmons Jul 15, 2026
8e36a24
fix(webportal): run export within session context
grantfitzsimmons Jul 15, 2026
e1fd11e
Potential fix for pull request finding 'CodeQL / Log Injection'
grantfitzsimmons Jul 15, 2026
4d5b3cb
fix(execution): yield tuples from post query processing to match test…
grantfitzsimmons Jul 15, 2026
ab8c1b0
fix(webportal): update type hint to reflect tuple output
grantfitzsimmons Jul 15, 2026
e1a06a5
Merge branch 'issue-7606' of https://github.com/specify/specify7 into…
grantfitzsimmons Jul 15, 2026
4be9315
test(csv): fix return_first mock signature to match post query processor
grantfitzsimmons Jul 15, 2026
52f0ccf
fix(tests): backend tests
grantfitzsimmons Jul 15, 2026
339f95a
test(queries): fix legacy test
grantfitzsimmons Jul 16, 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
151 changes: 89 additions & 62 deletions specifyweb/backend/inheritance/api.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,90 @@
from typing import Callable

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

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
specifyweb.specify.models
begins an import cycle.


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