From ed48352136438b2855b2805b26b7b1c95e355f10 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 25 Nov 2025 02:59:08 -0600 Subject: [PATCH 01/11] create indexes in models and migration files --- .../specify/migrations/0042_add_indexes.py | 735 ++++++++++++++++++ specifyweb/specify/models.py | 198 ++++- 2 files changed, 911 insertions(+), 22 deletions(-) create mode 100644 specifyweb/specify/migrations/0042_add_indexes.py diff --git a/specifyweb/specify/migrations/0042_add_indexes.py b/specifyweb/specify/migrations/0042_add_indexes.py new file mode 100644 index 00000000000..5db1d8bac30 --- /dev/null +++ b/specifyweb/specify/migrations/0042_add_indexes.py @@ -0,0 +1,735 @@ +# -*- coding: utf-8 -*- +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('specify', '0041_add_missing_schema_after_reorganization'), + ] + + operations = [ + # AgentIdentifier + migrations.AddIndex( + model_name='agentidentifier', + index=models.Index( + fields=['identifier'], + name='agentidentifier_identifier_idx', + ), + ), + migrations.AddIndex( + model_name='agentidentifier', + index=models.Index( + fields=['identifierType'], + name='agentidentifier_identifiertype_idx', + ), + ), + + # AgentSpecialty + migrations.AddIndex( + model_name='agentspecialty', + index=models.Index( + fields=['orderNumber'], + name='agentspecialty_ordernumber_idx', + ), + ), + migrations.AddIndex( + model_name='agentspecialty', + index=models.Index( + fields=['specialtyName'], + name='agentspecialty_specialtyname_idx', + ), + ), + + # AgentVariant + migrations.AddIndex( + model_name='agentvariant', + index=models.Index( + fields=['name'], + name='agentvariant_name_idx', + ), + ), + + # Attachment + migrations.AddIndex( + model_name='attachment', + index=models.Index( + fields=['origFilename'], + name='attachment_origfilename_idx', + ), + ), + + # Spdataset (attachmentdataset) + migrations.AddIndex( + model_name='spdataset', + index=models.Index( + fields=['name'], + name='spdataset_name_idx', + ), + ), + + # AttachmentMetadata + migrations.AddIndex( + model_name='attachmentmetadata', + index=models.Index( + fields=['name'], + name='attachmentmetadata_name_idx', + ), + ), + + # Author + migrations.AddIndex( + model_name='author', + index=models.Index( + fields=['orderNumber'], + name='author_ordernumber_idx', + ), + ), + + # CollectionObject + migrations.AddIndex( + model_name='collectionobject', + index=models.Index( + fields=['name'], + name='collectionobject_name_idx', + ), + ), + migrations.AddIndex( + model_name='collectionobject', + index=models.Index( + fields=['projectNumber'], + name='collectionobject_projectnumber_idx', + ), + ), + + # CollectionObjectGroup + migrations.AddIndex( + model_name='collectionobjectgroup', + index=models.Index( + fields=['guid'], + name='collectionobjectgroup_guid_idx', + ), + ), + migrations.AddIndex( + model_name='collectionobjectgroup', + index=models.Index( + fields=['name'], + name='collectionobjectgroup_name_idx', + ), + ), + + # CollectionObjectGroupType + migrations.AddIndex( + model_name='collectionobjectgrouptype', + index=models.Index( + fields=['name'], + name='collectionobjectgroupt_name_idx', + ), + ), + + # CollectionObjectProperty + migrations.AddIndex( + model_name='collectionobjectproperty', + index=models.Index( + fields=['guid'], + name='collectionobjectprop_guid_idx', + ), + ), + + # CollectionObjectType + migrations.AddIndex( + model_name='collectionobjecttype', + index=models.Index( + fields=['name'], + name='collectionobjecttype_name_idx', + ), + ), + + # CollectionRelType + migrations.AddIndex( + model_name='collectionreltype', + index=models.Index( + fields=['name'], + name='collectionreltype_name_idx', + ), + ), + + # ExchangeIn + migrations.AddIndex( + model_name='exchangein', + index=models.Index( + fields=['exchangeInNumber'], + name='exchangein_exchangeinnumber_idx', + ), + ), + + # ExsiccataItem + migrations.AddIndex( + model_name='exsiccataitem', + index=models.Index( + fields=['number'], + name='exsiccataitem_number_idx', + ), + ), + + # Geography + migrations.AddIndex( + model_name='geography', + index=models.Index( + fields=['commonName'], + name='geography_commonname_idx', + ), + ), + migrations.AddIndex( + model_name='geography', + index=models.Index( + fields=['guid'], + name='geography_guid_idx', + ), + ), + migrations.AddIndex( + model_name='geography', + index=models.Index( + fields=['highestChildNodeNumber'], + name='geography_highchildnodenumb_idx', + ), + ), + migrations.AddIndex( + model_name='geography', + index=models.Index( + fields=['nodeNumber'], + name='geography_nodenumber_idx', + ), + ), + + # GeographyTreeDef + migrations.AddIndex( + model_name='geographytreedef', + index=models.Index( + fields=['name'], + name='geographytreedef_name_idx', + ), + ), + + # GeographyTreeDefItem + migrations.AddIndex( + model_name='geographytreedefitem', + index=models.Index( + fields=['name'], + name='geogtreedefitem_name_idx', + ), + ), + + # GeologicTimePeriod + migrations.AddIndex( + model_name='geologictimeperiod', + index=models.Index( + fields=['highestChildNodeNumber'], + name='geotime_highchildnodenumb_idx', + ), + ), + migrations.AddIndex( + model_name='geologictimeperiod', + index=models.Index( + fields=['nodeNumber'], + name='geotime_nodenumber_idx', + ), + ), + + # GeologicTimePeriodTreeDef + migrations.AddIndex( + model_name='geologictimeperiodtreedef', + index=models.Index( + fields=['name'], + name='geotimetreedef_name_idx', + ), + ), + + # GeologicTimePeriodTreeDefItem + migrations.AddIndex( + model_name='geologictimeperiodtreedefitem', + index=models.Index( + fields=['name'], + name='geotimetreedefitem_name_idx', + ), + ), + + # InstitutionNetwork + migrations.AddIndex( + model_name='institutionnetwork', + index=models.Index( + fields=['altName'], + name='institutionnetwork_altname_idx', + ), + ), + + # LatLonPolygon + migrations.AddIndex( + model_name='latlonpolygon', + index=models.Index( + fields=['name'], + name='latlonpolygon_name_idx', + ), + ), + + # LithoStrat + migrations.AddIndex( + model_name='lithostrat', + index=models.Index( + fields=['highestChildNodeNumber'], + name='lithostrat_highchildnodenumb_idx', + ), + ), + migrations.AddIndex( + model_name='lithostrat', + index=models.Index( + fields=['nodeNumber'], + name='lithostrat_nodenumber_idx', + ), + ), + + # LithoStratTreeDef + migrations.AddIndex( + model_name='lithostrattreedef', + index=models.Index( + fields=['name'], + name='lithostratdef_name_idx', + ), + ), + + # LithoStratTreeDefItem + migrations.AddIndex( + model_name='lithostrattreedefitem', + index=models.Index( + fields=['name'], + name='lithostratdefitem_name_idx', + ), + ), + + # Locality + migrations.AddIndex( + model_name='locality', + index=models.Index( + fields=['guid'], + name='locality_guid_idx', + ), + ), + + # LocalityUpdateRowResult + migrations.AddIndex( + model_name='localityupdaterowresult', + index=models.Index( + fields=['rownumber'], + name='locupdaterow_rownumber_idx', + ), + ), + + # MaterialSample + migrations.AddIndex( + model_name='materialsample', + index=models.Index( + fields=['guid'], + name='materialsample_guid_idx', + ), + ), + + # MorphBankView + migrations.AddIndex( + model_name='morphbankview', + index=models.Index( + fields=['viewName'], + name='morphbankview_viewname_idx', + ), + ), + + # OtherIdentifier + migrations.AddIndex( + model_name='otheridentifier', + index=models.Index( + fields=['identifier'], + name='otheridentifier_identifier_idx', + ), + ), + + # PickList + migrations.AddIndex( + model_name='picklist', + index=models.Index( + fields=['fieldName'], + name='picklist_fieldname_idx', + ), + ), + migrations.AddIndex( + model_name='picklist', + index=models.Index( + fields=['filterFieldName'], + name='picklist_filterfieldname_idx', + ), + ), + migrations.AddIndex( + model_name='picklist', + index=models.Index( + fields=['tableName'], + name='picklist_tablename_idx', + ), + ), + + # PreparationProperty + migrations.AddIndex( + model_name='preparationproperty', + index=models.Index( + fields=['guid'], + name='preparationprop_guid_idx', + ), + ), + + # PrepType + migrations.AddIndex( + model_name='preptype', + index=models.Index( + fields=['name'], + name='preptype_name_idx', + ), + ), + + # ReferenceWork + migrations.AddIndex( + model_name='referencework', + index=models.Index( + fields=['libraryNumber'], + name='referencework_librarynumber_idx', + ), + ), + + # RelativeAge + migrations.AddIndex( + model_name='relativeage', + index=models.Index( + fields=['verbatimName'], + name='relativeage_verbatimname_idx', + ), + ), + + # SpAuditLogField + migrations.AddIndex( + model_name='spauditlogfield', + index=models.Index( + fields=['fieldName'], + name='spauditlogfield_fieldname_idx', + ), + ), + + # Spdataset (again; same as above, safe but redundant if both kept) + # (Kept single index definition above.) + + # SpecifyUser + migrations.AddIndex( + model_name='specifyuser', + index=models.Index( + fields=['name'], + name='specifyuser_name_idx', + ), + ), + + # SpExportSchema + migrations.AddIndex( + model_name='spexportschema', + index=models.Index( + fields=['schemaName'], + name='spexportschema_schemaname_idx', + ), + ), + + # SpExportSchemaItem + migrations.AddIndex( + model_name='spexportschemaitem', + index=models.Index( + fields=['fieldName'], + name='spexpschemaitem_fieldname_idx', + ), + ), + + # SpExportSchemaItemMapping + migrations.AddIndex( + model_name='spexportschemaitemmapping', + index=models.Index( + fields=['exportedFieldName'], + name='spexpschemaitemmap_expfield_idx', + ), + ), + + # SpExportSchemaMapping + migrations.AddIndex( + model_name='spexportschemamapping', + index=models.Index( + fields=['mappingName'], + name='spexpschemamap_mappingname_idx', + ), + ), + + # SpFieldValueDefault + migrations.AddIndex( + model_name='spfieldvaluedefault', + index=models.Index( + fields=['fieldName'], + name='spfieldvaluedef_fieldname_idx', + ), + ), + migrations.AddIndex( + model_name='spfieldvaluedefault', + index=models.Index( + fields=['tableName'], + name='spfieldvaluedef_tablename_idx', + ), + ), + + # LibraryRole (splibraryrole) + migrations.AddIndex( + model_name='libraryrole', + index=models.Index( + fields=['name'], + name='libraryrole_name_idx', + ), + ), + + # SpLocaleContainer + migrations.AddIndex( + model_name='splocalecontainer', + index=models.Index( + fields=['pickListName'], + name='splocalecont_picklistname_idx', + ), + ), + + # SpLocaleContainerItem + migrations.AddIndex( + model_name='splocalecontaineritem', + index=models.Index( + fields=['pickListName'], + name='splocalecontitem_picklist_idx', + ), + ), + migrations.AddIndex( + model_name='splocalecontaineritem', + index=models.Index( + fields=['webLinkName'], + name='splocalecontitem_weblink_idx', + ), + ), + + # SpMerging + migrations.AddIndex( + model_name='spmerging', + index=models.Index( + fields=['name'], + name='spmerging_name_idx', + ), + ), + + # SpPermission + migrations.AddIndex( + model_name='sppermission', + index=models.Index( + fields=['name'], + name='sppermission_name_idx', + ), + ), + + # SpPrincipal + migrations.AddIndex( + model_name='spprincipal', + index=models.Index( + fields=['name'], + name='spprincipal_name_idx', + ), + ), + + # SpQuery + migrations.AddIndex( + model_name='spquery', + index=models.Index( + fields=['contextName'], + name='spquery_contextname_idx', + ), + ), + + # SpQueryField + migrations.AddIndex( + model_name='spqueryfield', + index=models.Index( + fields=['fieldName'], + name='spqueryfield_fieldname_idx', + ), + ), + migrations.AddIndex( + model_name='spqueryfield', + index=models.Index( + fields=['formatName'], + name='spqueryfield_formatname_idx', + ), + ), + + # Role (sprole) + migrations.AddIndex( + model_name='role', + index=models.Index( + fields=['name'], + name='role_name_idx', + ), + ), + + # SpViewSetObj + migrations.AddIndex( + model_name='spviewsetobj', + index=models.Index( + fields=['fileName'], + name='spviewsetobj_filename_idx', + ), + ), + + # Storage + migrations.AddIndex( + model_name='storage', + index=models.Index( + fields=['highestChildNodeNumber'], + name='storage_highchildnodenumb_idx', + ), + ), + migrations.AddIndex( + model_name='storage', + index=models.Index( + fields=['nodeNumber'], + name='storage_nodenumber_idx', + ), + ), + + # StorageTreeDef + migrations.AddIndex( + model_name='storagetreedef', + index=models.Index( + fields=['name'], + name='storagetreedef_name_idx', + ), + ), + + # StorageTreeDefItem + migrations.AddIndex( + model_name='storagetreedefitem', + index=models.Index( + fields=['name'], + name='storagetreedefitem_name_idx', + ), + ), + + # Taxon + migrations.AddIndex( + model_name='taxon', + index=models.Index( + fields=['cultivarName'], + name='taxon_cultivarname_idx', + ), + ), + migrations.AddIndex( + model_name='taxon', + index=models.Index( + fields=['groupNumber'], + name='taxon_groupnumber_idx', + ), + ), + migrations.AddIndex( + model_name='taxon', + index=models.Index( + fields=['highestChildNodeNumber'], + name='taxon_highchildnodenumb_idx', + ), + ), + migrations.AddIndex( + model_name='taxon', + index=models.Index( + fields=['nodeNumber'], + name='taxon_nodenumber_idx', + ), + ), + + # TaxonTreeDef + migrations.AddIndex( + model_name='taxontreedef', + index=models.Index( + fields=['name'], + name='taxontreedef_name_idx', + ), + ), + + # TaxonTreeDefItem + migrations.AddIndex( + model_name='taxontreedefitem', + index=models.Index( + fields=['name'], + name='taxontreedefitem_name_idx', + ), + ), + + # TectonicUnit + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['fullName'], + name='tectonicunit_fullname_idx', + ), + ), + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['guid'], + name='tectonicunit_guid_idx', + ), + ), + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['highestChildNodeNumber'], + name='tectonicunit_highchildnumb_idx', + ), + ), + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['name'], + name='tectonicunit_name_idx', + ), + ), + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['nodeNumber'], + name='tectonicunit_nodenumber_idx', + ), + ), + + # TectonicUnitTreeDef + migrations.AddIndex( + model_name='tectonicunittreedef', + index=models.Index( + fields=['name'], + name='tectunitdef_name_idx', + ), + ), + + # TectonicUnitTreeDefItem + migrations.AddIndex( + model_name='tectonicunittreedefitem', + index=models.Index( + fields=['name'], + name='tectunitdefitem_name_idx', + ), + ), + + # VoucherRelationship + migrations.AddIndex( + model_name='voucherrelationship', + index=models.Index( + fields=['voucherNumber'], + name='voucherrel_vouchernumber_idx', + ), + ), + ] \ No newline at end of file diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index 4e991d02ecb..86c27d935a4 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -431,6 +431,10 @@ class Agentidentifier(models.Model): class Meta: db_table = 'agentidentifier' ordering = () + indexes = [ + models.Index(fields=['identifier'], name='agentidentifier_identifier_idx'), + models.Index(fields=['identifiertype'], name='agentidentifier_identifiertype_idx'), + ] save = partialmethod(custom_save) @@ -457,6 +461,10 @@ class Meta: db_table = 'agentspecialty' ordering = () unique_together = (('agent', 'ordernumber'),) + indexes = [ + models.Index(fields=['ordernumber'], name='agentspecialty_ordernumber_idx'), + models.Index(fields=['specialtyname'], name='agentspecialty_specialtyname_idx'), + ] save = partialmethod(custom_save) @@ -485,6 +493,9 @@ class Agentvariant(models.Model): class Meta: db_table = 'agentvariant' ordering = () + indexes = [ + models.Index(fields=['name'], name='agentvariant_name_idx'), + ] save = partialmethod(custom_save) @@ -572,7 +583,8 @@ class Meta: models.Index(fields=['dateimaged'], name='DateImagedIDX'), models.Index(fields=['scopeid'], name='AttchScopeIDIDX'), models.Index(fields=['scopetype'], name='AttchScopeTypeIDX'), - models.Index(fields=['guid'], name='AttchmentGuidIDX') + models.Index(fields=['guid'], name='AttchmentGuidIDX'), + models.Index(fields=['origfilename'], name='attachment_origfilename_idx'), ] @@ -639,6 +651,9 @@ class Attachmentmetadata(models.Model): class Meta: db_table = 'attachmentmetadata' ordering = () + indexes = [ + models.Index(fields=['name'], name='attachmentmetadata_name_idx'), + ] save = partialmethod(custom_save) @@ -717,6 +732,9 @@ class Meta: db_table = 'author' ordering = ('ordernumber',) unique_together = (('referencework', 'agent'),) + indexes = [ + models.Index(fields=['ordernumber'], name='author_ordernumber_idx'), + ] save = partialmethod(custom_save) @@ -1490,7 +1508,9 @@ class Meta: models.Index(fields=['uniqueidentifier'], name='COUniqueIdentifierIDX'), models.Index(fields=['altcatalognumber'], name='AltCatalogNumberIDX'), models.Index(fields=['guid'], name='ColObjGuidIDX'), - models.Index(fields=['collectionmemberid'], name='COColMemIDX') + models.Index(fields=['collectionmemberid'], name='COColMemIDX'), + models.Index(fields=['name'], name='collectionobject_name_idx'), + models.Index(fields=['projectnumber'], name='collectionobject_projectnumber_idx'), ] @@ -1921,7 +1941,8 @@ class Meta: db_table = 'collectionobjectproperty' ordering = () indexes = [ - models.Index(fields=['collectionmemberid'], name='COLOBJPROPColMemIDX') + models.Index(fields=['collectionmemberid'], name='COLOBJPROPColMemIDX'), + models.Index(fields=['guid'], name='collectionobjectprop_guid_idx'), ] @@ -1949,6 +1970,9 @@ class Collectionreltype(models.Model): class Meta: db_table = 'collectionreltype' ordering = () + indexes = [ + models.Index(fields=['name'], name='collectionreltype_name_idx'), + ] save = partialmethod(custom_save) @@ -3096,7 +3120,8 @@ class Meta: ordering = () indexes = [ models.Index(fields=['exchangedate'], name='ExchangeDateIDX'), - models.Index(fields=['descriptionofmaterial'], name='DescriptionOfMaterialIDX') + models.Index(fields=['descriptionofmaterial'], name='DescriptionOfMaterialIDX'), + models.Index(fields=['exchangeinnumber'], name='exchangein_exchangeinnumber_idx'), ] @@ -3316,6 +3341,9 @@ class Exsiccataitem(models.Model): class Meta: db_table = 'exsiccataitem' ordering = () + indexes = [ + models.Index(fields=['number'], name='exsiccataitem_number_idx'), + ] save = partialmethod(custom_save) @@ -3668,7 +3696,11 @@ class Meta: ordering = () indexes = [ models.Index(fields=['name'], name='GeoNameIDX'), - models.Index(fields=['fullname'], name='GeoFullNameIDX') + models.Index(fields=['fullname'], name='GeoFullNameIDX'), + models.Index(fields=['commonname'], name='geography_commonname_idx'), + models.Index(fields=['guid'], name='geography_guid_idx'), + models.Index(fields=['highestchildnodenumber'], name='geography_highchildnodenumb_idx'), + models.Index(fields=['nodenumber'], name='geography_nodenumber_idx'), ] @@ -3696,6 +3728,9 @@ class Geographytreedef(models.Model): class Meta: db_table = 'geographytreedef' ordering = () + indexes = [ + models.Index(fields=['name'], name='geographytreedef_name_idx'), + ] save = partialmethod(custom_save) @@ -3729,6 +3764,9 @@ class Geographytreedefitem(model_extras.Geographytreedefitem): class Meta: db_table = 'geographytreedefitem' ordering = () + indexes = [ + models.Index(fields=['name'], name='geogtreedefitem_name_idx'), + ] save = partialmethod(custom_save) @@ -3774,7 +3812,9 @@ class Meta: indexes = [ models.Index(fields=['name'], name='GTPNameIDX'), models.Index(fields=['fullname'], name='GTPFullNameIDX'), - models.Index(fields=['guid'], name='GTPGuidIDX') + models.Index(fields=['guid'], name='GTPGuidIDX'), + models.Index(fields=['highestchildnodenumber'], name='geotime_highchildnodenumb_idx'), + models.Index(fields=['nodenumber'], name='geotime_nodenumber_idx'), ] @@ -3802,6 +3842,9 @@ class Geologictimeperiodtreedef(models.Model): class Meta: db_table = 'geologictimeperiodtreedef' ordering = () + indexes = [ + models.Index(fields=['name'], name='geotimetreedef_name_idx'), + ] save = partialmethod(custom_save) @@ -3835,6 +3878,9 @@ class Geologictimeperiodtreedefitem(model_extras.Geologictimeperiodtreedefitem): class Meta: db_table = 'geologictimeperiodtreedefitem' ordering = () + indexes = [ + models.Index(fields=['name'], name='geotimetreedefitem_name_idx'), + ] save = partialmethod(custom_save) @@ -4147,7 +4193,8 @@ class Meta: db_table = 'institutionnetwork' ordering = () indexes = [ - models.Index(fields=['name'], name='InstNetworkNameIDX') + models.Index(fields=['name'], name='InstNetworkNameIDX'), + models.Index(fields=['altname'], name='institutionnetwork_altname_idx'), ] @@ -4209,6 +4256,9 @@ class Latlonpolygon(models.Model): class Meta: db_table = 'latlonpolygon' ordering = () + indexes = [ + models.Index(fields=['name'], name='latlonpolygon_name_idx'), + ] save = partialmethod(custom_save) @@ -4273,7 +4323,9 @@ class Meta: indexes = [ models.Index(fields=['name'], name='LithoNameIDX'), models.Index(fields=['fullname'], name='LithoFullNameIDX'), - models.Index(fields=['guid'], name='LithoGuidIDX') + models.Index(fields=['guid'], name='LithoGuidIDX'), + models.Index(fields=['highestchildnodenumber'], name='lithostrat_highchildnodenumb_idx'), + models.Index(fields=['nodenumber'], name='lithostrat_nodenumber_idx'), ] @@ -4301,6 +4353,9 @@ class Lithostrattreedef(models.Model): class Meta: db_table = 'lithostrattreedef' ordering = () + indexes = [ + models.Index(fields=['name'], name='lithostratdef_name_idx'), + ] save = partialmethod(custom_save) @@ -4334,6 +4389,9 @@ class Lithostrattreedefitem(model_extras.Lithostrattreedefitem): class Meta: db_table = 'lithostrattreedefitem' ordering = () + indexes = [ + models.Index(fields=['name'], name='lithostratdefitem_name_idx'), + ] save = partialmethod(custom_save) @@ -4603,7 +4661,8 @@ class Meta: models.Index(fields=['discipline'], name='LocalityDisciplineIDX'), models.Index(fields=['namedplace'], name='NamedPlaceIDX'), models.Index(fields=['uniqueidentifier'], name='LocalityUniqueIdentifierIDX'), - models.Index(fields=['relationtonamedplace'], name='RelationToNamedPlaceIDX') + models.Index(fields=['relationtonamedplace'], name='RelationToNamedPlaceIDX'), + models.Index(fields=['guid'], name='locality_guid_idx'), ] @@ -4829,7 +4888,8 @@ class Meta: db_table = 'materialsample' ordering = () indexes = [ - models.Index(fields=['ggbn_sampledesignation'], name='DesignationIDX') + models.Index(fields=['ggbn_sampledesignation'], name='DesignationIDX'), + models.Index(fields=['guid'], name='materialsample_guid_idx'), ] @@ -4862,6 +4922,9 @@ class Morphbankview(models.Model): class Meta: db_table = 'morphbankview' ordering = () + indexes = [ + models.Index(fields=['viewname'], name='morphbankview_viewname_idx'), + ] save = partialmethod(custom_save) @@ -4905,6 +4968,9 @@ class Otheridentifier(models.Model): class Meta: db_table = 'otheridentifier' ordering = () + indexes = [ + models.Index(fields=['identifier'], name='otheridentifier_identifier_idx'), + ] indexes = [ models.Index(fields=['collectionmemberid'], name='OthIdColMemIDX') ] @@ -5101,7 +5167,10 @@ class Meta: db_table = 'picklist' ordering = () indexes = [ - models.Index(fields=['name'], name='PickListNameIDX') + models.Index(fields=['name'], name='PickListNameIDX'), + models.Index(fields=['fieldname'], name='picklist_fieldname_idx'), + models.Index(fields=['filterfieldname'], name='picklist_filterfieldname_idx'), + models.Index(fields=['tablename'], name='picklist_tablename_idx'), ] @@ -5154,6 +5223,9 @@ class Preptype(models.Model): class Meta: db_table = 'preptype' ordering = () + indexes = [ + models.Index(fields=['name'], name='preptype_name_idx'), + ] save = partialmethod(custom_save) @@ -5545,7 +5617,8 @@ class Meta: db_table = 'preparationproperty' ordering = () indexes = [ - models.Index(fields=['collectionmemberid'], name='PREPPROPColMemIDX') + models.Index(fields=['collectionmemberid'], name='PREPPROPColMemIDX'), + models.Index(fields=['guid'], name='preparationprop_guid_idx'), ] @@ -5695,7 +5768,8 @@ class Meta: models.Index(fields=['title'], name='RefWrkTitleIDX'), models.Index(fields=['publisher'], name='RefWrkPublisherIDX'), models.Index(fields=['guid'], name='RefWrkGuidIDX'), - models.Index(fields=['isbn'], name='ISBNIDX') + models.Index(fields=['isbn'], name='ISBNIDX'), + models.Index(fields=['librarynumber'], name='referencework_librarynumber_idx'), ] @@ -6019,6 +6093,9 @@ class Spauditlogfield(models.Model): class Meta: db_table = 'spauditlogfield' ordering = () + indexes = [ + models.Index(fields=['fieldname'], name='spauditlogfield_fieldname_idx'), + ] save = partialmethod(custom_save) @@ -6045,6 +6122,9 @@ class Spexportschema(models.Model): class Meta: db_table = 'spexportschema' ordering = () + indexes = [ + models.Index(fields=['schemaname'], name='spexportschema_schemaname_idx'), + ] save = partialmethod(custom_save) @@ -6073,6 +6153,9 @@ class Spexportschemaitem(models.Model): class Meta: db_table = 'spexportschemaitem' ordering = () + indexes = [ + models.Index(fields=['fieldname'], name='spexpschemaitem_fieldname_idx'), + ] save = partialmethod(custom_save) @@ -6102,6 +6185,9 @@ class Spexportschemaitemmapping(models.Model): class Meta: db_table = 'spexportschemaitemmapping' ordering = () + indexes = [ + models.Index(fields=['exportedfieldname'], name='spexpschemaitemmap_expfield_idx'), + ] save = partialmethod(custom_save) @@ -6129,7 +6215,8 @@ class Meta: db_table = 'spexportschemamapping' ordering = () indexes = [ - models.Index(fields=['collectionmemberid'], name='SPEXPSCHMMAPColMemIDX') + models.Index(fields=['collectionmemberid'], name='SPEXPSCHMMAPColMemIDX'), + models.Index(fields=['mappingname'], name='spexpschemamap_mappingname_idx'), ] @@ -6159,7 +6246,9 @@ class Meta: db_table = 'spfieldvaluedefault' ordering = () indexes = [ - models.Index(fields=['collectionmemberid'], name='SpFieldValueDefaultColMemIDX') + models.Index(fields=['collectionmemberid'], name='SpFieldValueDefaultColMemIDX'), + models.Index(fields=['fieldname'], name='spfieldvaluedef_fieldname_idx'), + models.Index(fields=['tablename'], name='spfieldvaluedef_tablename_idx'), ] @@ -6195,7 +6284,8 @@ class Meta: db_table = 'splocalecontainer' ordering = () indexes = [ - models.Index(fields=['name'], name='SpLocaleContainerNameIDX') + models.Index(fields=['name'], name='SpLocaleContainerNameIDX'), + models.Index(fields=['picklistname'], name='splocalecont_picklistname_idx'), ] @@ -6230,7 +6320,9 @@ class Meta: db_table = 'splocalecontaineritem' ordering = () indexes = [ - models.Index(fields=['name'], name='SpLocaleContainerItemNameIDX') + models.Index(fields=['name'], name='SpLocaleContainerItemNameIDX'), + models.Index(fields=['picklistname'], name='splocalecontitem_picklist_idx'), + models.Index(fields=['weblinkname'], name='splocalecontitem_weblink_idx'), ] @@ -6285,6 +6377,9 @@ class Sppermission(models.Model): class Meta: db_table = 'sppermission' ordering = () + indexes = [ + models.Index(fields=['name'], name='sppermission_name_idx'), + ] save = partialmethod(custom_save) @@ -6312,6 +6407,9 @@ class Spprincipal(models.Model): class Meta: db_table = 'spprincipal' ordering = () + indexes = [ + models.Index(fields=['name'], name='spprincipal_name_idx'), + ] save = partialmethod(custom_save) @@ -6348,7 +6446,8 @@ class Meta: db_table = 'spquery' ordering = () indexes = [ - models.Index(fields=['name'], name='SpQueryNameIDX') + models.Index(fields=['name'], name='SpQueryNameIDX'), + models.Index(fields=['contextname'], name='spquery_contextname_idx'), ] @@ -6392,6 +6491,10 @@ class Spqueryfield(models.Model): class Meta: db_table = 'spqueryfield' ordering = ('position',) + indexes = [ + models.Index(fields=['fieldname'], name='spqueryfield_fieldname_idx'), + models.Index(fields=['formatname'], name='spqueryfield_formatname_idx'), + ] save = partialmethod(custom_save) @@ -6550,7 +6653,8 @@ class Meta: db_table = 'spviewsetobj' ordering = () indexes = [ - models.Index(fields=['name'], name='SpViewObjNameIDX') + models.Index(fields=['name'], name='SpViewObjNameIDX'), + models.Index(fields=['filename'], name='spviewsetobj_filename_idx'), ] @@ -6612,6 +6716,9 @@ class Specifyuser(model_extras.Specifyuser): class Meta: db_table = 'specifyuser' ordering = () + indexes = [ + models.Index(fields=['name'], name='specifyuser_name_idx'), + ] # save = partialmethod(custom_save) @@ -6654,7 +6761,9 @@ class Meta: ordering = () indexes = [ models.Index(fields=['name'], name='StorNameIDX'), - models.Index(fields=['fullname'], name='StorFullNameIDX') + models.Index(fields=['fullname'], name='StorFullNameIDX'), + models.Index(fields=['highestchildnodenumber'], name='storage_highchildnodenumb_idx'), + models.Index(fields=['nodenumber'], name='storage_nodenumber_idx'), ] @@ -6708,6 +6817,9 @@ class Storagetreedef(models.Model): class Meta: db_table = 'storagetreedef' ordering = () + indexes = [ + models.Index(fields=['name'], name='storagetreedef_name_idx'), + ] save = partialmethod(custom_save) @@ -6741,6 +6853,9 @@ class Storagetreedefitem(model_extras.Storagetreedefitem): class Meta: db_table = 'storagetreedefitem' ordering = () + indexes = [ + models.Index(fields=['name'], name='storagetreedefitem_name_idx'), + ] save = partialmethod(custom_save) @@ -6859,7 +6974,11 @@ class Meta: models.Index(fields=['commonname'], name='TaxonCommonNameIDX'), models.Index(fields=['name'], name='TaxonNameIDX'), models.Index(fields=['fullname'], name='TaxonFullNameIDX'), - models.Index(fields=['environmentalprotectionstatus'], name='EPSIDX') # Avoid error: The index name 'EnvironmentalProtectionStatusIDX' cannot be longer than 30 characters. + models.Index(fields=['environmentalprotectionstatus'], name='EPSIDX'), # Avoid error: The index name 'EnvironmentalProtectionStatusIDX' cannot be longer than 30 characters. + models.Index(fields=['cultivarname'], name='taxon_cultivarname_idx'), + models.Index(fields=['groupnumber'], name='taxon_groupnumber_idx'), + models.Index(fields=['highestchildnodenumber'], name='taxon_highchildnodenumb_idx'), + models.Index(fields=['nodenumber'], name='taxon_nodenumber_idx'), ] @@ -7136,6 +7255,9 @@ class Taxontreedef(models.Model): class Meta: db_table = 'taxontreedef' ordering = () + indexes = [ + models.Index(fields=['name'], name='taxontreedef_name_idx'), + ] save = partialmethod(custom_save) @@ -7170,6 +7292,9 @@ class Taxontreedefitem(model_extras.Taxontreedefitem): class Meta: db_table = 'taxontreedefitem' ordering = () + indexes = [ + models.Index(fields=['name'], name='taxontreedefitem_name_idx'), + ] save = partialmethod(custom_save) @@ -7295,6 +7420,9 @@ class Voucherrelationship(models.Model): class Meta: db_table = 'voucherrelationship' ordering = () + indexes = [ + models.Index(fields=['vouchernumber'], name='voucherrel_vouchernumber_idx'), + ] indexes = [ models.Index(fields=['collectionmemberid'], name='VRXDATColMemIDX') ] @@ -7595,6 +7723,9 @@ class Collectionobjecttype(models.Model): class Meta: db_table = 'collectionobjecttype' ordering = () + indexes = [ + models.Index(fields=['name'], name='collectionobjecttype_name_idx'), + ] save = partialmethod(custom_save) @@ -7619,6 +7750,9 @@ class Collectionobjectgrouptype(models.Model): class Meta: db_table = 'collectionobjectgrouptype' ordering = () + indexes = [ + models.Index(fields=['name'], name='collectionobjectgroupt_name_idx'), + ] save = partialmethod(custom_save) @@ -7658,6 +7792,10 @@ class Collectionobjectgroup(models.Model): # aka. Cog class Meta: db_table = 'collectionobjectgroup' ordering = () + indexes = [ + models.Index(fields=['guid'], name='collectionobjectgroup_guid_idx'), + models.Index(fields=['name'], name='collectionobjectgroup_name_idx'), + ] save = partialmethod(custom_save) @@ -7781,6 +7919,9 @@ class Relativeage(models.Model): class Meta: db_table = 'relativeage' ordering = () + indexes = [ + models.Index(fields=['verbatimname'], name='relativeage_verbatimname_idx'), + ] save = partialmethod(custom_save) @@ -7916,6 +8057,9 @@ class Tectonicunittreedef(models.Model): class Meta: db_table = 'tectonicunittreedef' ordering = () + indexes = [ + models.Index(fields=['name'], name='tectunitdef_name_idx'), + ] save = partialmethod(custom_save) @@ -7948,6 +8092,9 @@ class Tectonicunittreedefitem(model_extras.Tectonicunittreedefitem): class Meta: db_table = 'tectonicunittreedefitem' ordering = () + indexes = [ + models.Index(fields=['name'], name='tectunitdefitem_name_idx'), + ] save = partialmethod(custom_save) @@ -7987,5 +8134,12 @@ class Tectonicunit(model_extras.Tectonicunit): class Meta: db_table = 'tectonicunit' ordering = () + indexes = [ + models.Index(fields=['fullname'], name='tectonicunit_fullname_idx'), + models.Index(fields=['guid'], name='tectonicunit_guid_idx'), + models.Index(fields=['highestchildnodenumber'], name='tectonicunit_highchildnumb_idx'), + models.Index(fields=['name'], name='tectonicunit_name_idx'), + models.Index(fields=['nodenumber'], name='tectonicunit_nodenumber_idx'), + ] - save = partialmethod(custom_save) \ No newline at end of file + save = partialmethod(custom_save) From 7f4bf11f6378d11c772844c76b8e3b803ba0b6c1 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 13 Mar 2026 15:20:44 +0000 Subject: [PATCH 02/11] Lint code with ESLint and Prettier Triggered by 66a7b8420f26da7ac42ca751853ee1194c1288ce on branch refs/heads/issue-7482 --- .../frontend/js_src/lib/components/Atoms/Form.tsx | 4 ++-- .../Attachments/__tests__/uploadFile.test.ts | 11 +++++++---- .../lib/components/FormSliders/RecordSelector.tsx | 7 ++++--- .../js_src/lib/components/QueryBuilder/Results.tsx | 10 ++++++---- .../lib/components/WbImportAttachments/index.tsx | 2 +- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/Form.tsx b/specifyweb/frontend/js_src/lib/components/Atoms/Form.tsx index 0c925b1eb2a..07902bbb7aa 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/Form.tsx +++ b/specifyweb/frontend/js_src/lib/components/Atoms/Form.tsx @@ -354,8 +354,8 @@ export const Select = wrap< */ if (props.required !== true && props.multiple === true) { selected.map((option) => option.classList.add('dark:bg-neutral-500')); // Highlights selected object less bright - unselected.map((option) => - option.classList.remove('dark:bg-neutral-500') // Prevents a previously selected option from remaining highlighted + unselected.map( + (option) => option.classList.remove('dark:bg-neutral-500') // Prevents a previously selected option from remaining highlighted ); } const value = (event.target as HTMLSelectElement).value; diff --git a/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/uploadFile.test.ts b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/uploadFile.test.ts index 8eddbdf7907..7439350e268 100644 --- a/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/uploadFile.test.ts +++ b/specifyweb/frontend/js_src/lib/components/Attachments/__tests__/uploadFile.test.ts @@ -31,12 +31,15 @@ describe('uploadFile', () => { return { open: jest.fn(), send: jest.fn((..._args: readonly unknown[]) => listeners[nextEvent]?.()), - addEventListener: jest.fn((eventName: EventName, callback: () => void) => { - listeners[eventName] = callback; - }), + addEventListener: jest.fn( + (eventName: EventName, callback: () => void) => { + listeners[eventName] = callback; + } + ), removeEventListener: jest.fn( (eventName: EventName, callback: () => void) => { - if (listeners[eventName] === callback) listeners[eventName] = undefined; + if (listeners[eventName] === callback) + listeners[eventName] = undefined; } ), upload: { diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx index 8c203cfc15e..ea57737d30d 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx @@ -84,9 +84,10 @@ export function useRecordSelector({ [index] ); - const isToOne = field === undefined - ? false - : !relationshipIsToMany(field) || shouldBeToOne(field); + const isToOne = + field === undefined + ? false + : !relationshipIsToMany(field) || shouldBeToOne(field); const handleResourcesSelected = React.useMemo( () => diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Results.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Results.tsx index d46aa5b10ce..e9124f832c4 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Results.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Results.tsx @@ -149,9 +149,9 @@ export function QueryResults(props: QueryResultsProps): JSX.Element { const loadedResults = ( undefinedResult === -1 ? results : results?.slice(0, undefinedResult) ) as RA | undefined; - + /* eslint-disable functional/prefer-readonly-type */ - const deletingRef = React.useRef>(new Set()); // Track recent deleted IDs to prevent duplicate deletion + const deletingRef = React.useRef>(new Set()); // Track recent deleted IDs to prevent duplicate deletion // TEST: try deleting while records are being fetched /** @@ -161,7 +161,7 @@ export function QueryResults(props: QueryResultsProps): JSX.Element { (recordId: number): void => { if (deletingRef.current.has(recordId)) return; // Prevents duplicate deletion calls for the same record deletingRef.current.add(recordId); - + let removeCount = 0; function newResults(results: RA | undefined) { if (!Array.isArray(results) || totalCount === undefined) return; @@ -179,7 +179,9 @@ export function QueryResults(props: QueryResultsProps): JSX.Element { return; } setTotalCount((totalCount) => - totalCount === undefined ? undefined : Math.max(0, totalCount - removeCount) + totalCount === undefined + ? undefined + : Math.max(0, totalCount - removeCount) ); const newSelectedRows = (selectedRows: ReadonlySet) => new Set(Array.from(selectedRows).filter((id) => id !== recordId)); diff --git a/specifyweb/frontend/js_src/lib/components/WbImportAttachments/index.tsx b/specifyweb/frontend/js_src/lib/components/WbImportAttachments/index.tsx index aeee2eb53b1..4ef8a49f3c2 100644 --- a/specifyweb/frontend/js_src/lib/components/WbImportAttachments/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/WbImportAttachments/index.tsx @@ -138,7 +138,7 @@ function FilesPicked({ files }: { readonly files: RA }): JSX.Element { setFileUploadProgress(0); return Promise.resolve() - .then(() => + .then(async () => Promise.all( uploadFiles(files, setFileUploadProgress, attachmentIsPublicDefault) ) From bebca3c3b86a3ff29d9d3b196d7d3b0c6ade1530 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 13 Mar 2026 10:30:36 -0500 Subject: [PATCH 03/11] update migration name --- .../migrations/{0042_add_indexes.py => 0045_add_indexes.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename specifyweb/specify/migrations/{0042_add_indexes.py => 0045_add_indexes.py} (99%) diff --git a/specifyweb/specify/migrations/0042_add_indexes.py b/specifyweb/specify/migrations/0045_add_indexes.py similarity index 99% rename from specifyweb/specify/migrations/0042_add_indexes.py rename to specifyweb/specify/migrations/0045_add_indexes.py index 5db1d8bac30..51fdc604665 100644 --- a/specifyweb/specify/migrations/0042_add_indexes.py +++ b/specifyweb/specify/migrations/0045_add_indexes.py @@ -5,7 +5,7 @@ class Migration(migrations.Migration): dependencies = [ - ('specify', '0041_add_missing_schema_after_reorganization'), + ('specify', '0044_alter_deletion_cascade'), ] operations = [ From 4688cc4584f4944c32803fc1fa761d8430aeee18 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 13 Mar 2026 12:06:07 -0500 Subject: [PATCH 04/11] Fix field naming and remove indexes that aren't needed --- .../specify/migrations/0045_add_indexes.py | 366 +++++++----------- specifyweb/specify/models.py | 4 - 2 files changed, 150 insertions(+), 220 deletions(-) diff --git a/specifyweb/specify/migrations/0045_add_indexes.py b/specifyweb/specify/migrations/0045_add_indexes.py index 51fdc604665..01157df997e 100644 --- a/specifyweb/specify/migrations/0045_add_indexes.py +++ b/specifyweb/specify/migrations/0045_add_indexes.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): ] operations = [ - # AgentIdentifier + # Agentidentifier migrations.AddIndex( model_name='agentidentifier', index=models.Index( @@ -20,28 +20,28 @@ class Migration(migrations.Migration): migrations.AddIndex( model_name='agentidentifier', index=models.Index( - fields=['identifierType'], + fields=['identifiertype'], name='agentidentifier_identifiertype_idx', ), ), - # AgentSpecialty + # Agentspecialty migrations.AddIndex( model_name='agentspecialty', index=models.Index( - fields=['orderNumber'], + fields=['ordernumber'], name='agentspecialty_ordernumber_idx', ), ), migrations.AddIndex( model_name='agentspecialty', index=models.Index( - fields=['specialtyName'], + fields=['specialtyname'], name='agentspecialty_specialtyname_idx', ), ), - # AgentVariant + # Agentvariant migrations.AddIndex( model_name='agentvariant', index=models.Index( @@ -50,25 +50,7 @@ class Migration(migrations.Migration): ), ), - # Attachment - migrations.AddIndex( - model_name='attachment', - index=models.Index( - fields=['origFilename'], - name='attachment_origfilename_idx', - ), - ), - - # Spdataset (attachmentdataset) - migrations.AddIndex( - model_name='spdataset', - index=models.Index( - fields=['name'], - name='spdataset_name_idx', - ), - ), - - # AttachmentMetadata + # Attachmentmetadata migrations.AddIndex( model_name='attachmentmetadata', index=models.Index( @@ -81,12 +63,12 @@ class Migration(migrations.Migration): migrations.AddIndex( model_name='author', index=models.Index( - fields=['orderNumber'], + fields=['ordernumber'], name='author_ordernumber_idx', ), ), - # CollectionObject + # Collectionobject migrations.AddIndex( model_name='collectionobject', index=models.Index( @@ -97,37 +79,12 @@ class Migration(migrations.Migration): migrations.AddIndex( model_name='collectionobject', index=models.Index( - fields=['projectNumber'], + fields=['projectnumber'], name='collectionobject_projectnumber_idx', ), ), - # CollectionObjectGroup - migrations.AddIndex( - model_name='collectionobjectgroup', - index=models.Index( - fields=['guid'], - name='collectionobjectgroup_guid_idx', - ), - ), - migrations.AddIndex( - model_name='collectionobjectgroup', - index=models.Index( - fields=['name'], - name='collectionobjectgroup_name_idx', - ), - ), - - # CollectionObjectGroupType - migrations.AddIndex( - model_name='collectionobjectgrouptype', - index=models.Index( - fields=['name'], - name='collectionobjectgroupt_name_idx', - ), - ), - - # CollectionObjectProperty + # Collectionobjectproperty migrations.AddIndex( model_name='collectionobjectproperty', index=models.Index( @@ -136,16 +93,7 @@ class Migration(migrations.Migration): ), ), - # CollectionObjectType - migrations.AddIndex( - model_name='collectionobjecttype', - index=models.Index( - fields=['name'], - name='collectionobjecttype_name_idx', - ), - ), - - # CollectionRelType + # Collectionreltype migrations.AddIndex( model_name='collectionreltype', index=models.Index( @@ -154,16 +102,16 @@ class Migration(migrations.Migration): ), ), - # ExchangeIn + # Exchangein migrations.AddIndex( model_name='exchangein', index=models.Index( - fields=['exchangeInNumber'], + fields=['exchangeinnumber'], name='exchangein_exchangeinnumber_idx', ), ), - # ExsiccataItem + # Exsiccataitem migrations.AddIndex( model_name='exsiccataitem', index=models.Index( @@ -176,7 +124,7 @@ class Migration(migrations.Migration): migrations.AddIndex( model_name='geography', index=models.Index( - fields=['commonName'], + fields=['commonname'], name='geography_commonname_idx', ), ), @@ -190,19 +138,19 @@ class Migration(migrations.Migration): migrations.AddIndex( model_name='geography', index=models.Index( - fields=['highestChildNodeNumber'], + fields=['highestchildnodenumber'], name='geography_highchildnodenumb_idx', ), ), migrations.AddIndex( model_name='geography', index=models.Index( - fields=['nodeNumber'], + fields=['nodenumber'], name='geography_nodenumber_idx', ), ), - # GeographyTreeDef + # Geographytreedef migrations.AddIndex( model_name='geographytreedef', index=models.Index( @@ -211,7 +159,7 @@ class Migration(migrations.Migration): ), ), - # GeographyTreeDefItem + # Geographytreedefitem migrations.AddIndex( model_name='geographytreedefitem', index=models.Index( @@ -220,23 +168,23 @@ class Migration(migrations.Migration): ), ), - # GeologicTimePeriod + # Geologictimeperiod migrations.AddIndex( model_name='geologictimeperiod', index=models.Index( - fields=['highestChildNodeNumber'], + fields=['highestchildnodenumber'], name='geotime_highchildnodenumb_idx', ), ), migrations.AddIndex( model_name='geologictimeperiod', index=models.Index( - fields=['nodeNumber'], + fields=['nodenumber'], name='geotime_nodenumber_idx', ), ), - # GeologicTimePeriodTreeDef + # Geologictimeperiodtreedef migrations.AddIndex( model_name='geologictimeperiodtreedef', index=models.Index( @@ -245,7 +193,7 @@ class Migration(migrations.Migration): ), ), - # GeologicTimePeriodTreeDefItem + # Geologictimeperiodtreedefitem migrations.AddIndex( model_name='geologictimeperiodtreedefitem', index=models.Index( @@ -254,16 +202,16 @@ class Migration(migrations.Migration): ), ), - # InstitutionNetwork + # Institutionnetwork migrations.AddIndex( model_name='institutionnetwork', index=models.Index( - fields=['altName'], + fields=['altname'], name='institutionnetwork_altname_idx', ), ), - # LatLonPolygon + # Latlonpolygon migrations.AddIndex( model_name='latlonpolygon', index=models.Index( @@ -272,23 +220,23 @@ class Migration(migrations.Migration): ), ), - # LithoStrat + # Lithostrat migrations.AddIndex( model_name='lithostrat', index=models.Index( - fields=['highestChildNodeNumber'], + fields=['highestchildnodenumber'], name='lithostrat_highchildnodenumb_idx', ), ), migrations.AddIndex( model_name='lithostrat', index=models.Index( - fields=['nodeNumber'], + fields=['nodenumber'], name='lithostrat_nodenumber_idx', ), ), - # LithoStratTreeDef + # Lithostrattreedef migrations.AddIndex( model_name='lithostrattreedef', index=models.Index( @@ -297,7 +245,7 @@ class Migration(migrations.Migration): ), ), - # LithoStratTreeDefItem + # Lithostrattreedefitem migrations.AddIndex( model_name='lithostrattreedefitem', index=models.Index( @@ -315,16 +263,7 @@ class Migration(migrations.Migration): ), ), - # LocalityUpdateRowResult - migrations.AddIndex( - model_name='localityupdaterowresult', - index=models.Index( - fields=['rownumber'], - name='locupdaterow_rownumber_idx', - ), - ), - - # MaterialSample + # Materialsample migrations.AddIndex( model_name='materialsample', index=models.Index( @@ -333,16 +272,16 @@ class Migration(migrations.Migration): ), ), - # MorphBankView + # Morphbankview migrations.AddIndex( model_name='morphbankview', index=models.Index( - fields=['viewName'], + fields=['viewname'], name='morphbankview_viewname_idx', ), ), - # OtherIdentifier + # Otheridentifier migrations.AddIndex( model_name='otheridentifier', index=models.Index( @@ -351,39 +290,30 @@ class Migration(migrations.Migration): ), ), - # PickList + # Picklist migrations.AddIndex( model_name='picklist', index=models.Index( - fields=['fieldName'], + fields=['fieldname'], name='picklist_fieldname_idx', ), ), migrations.AddIndex( model_name='picklist', index=models.Index( - fields=['filterFieldName'], + fields=['filterfieldname'], name='picklist_filterfieldname_idx', ), ), migrations.AddIndex( model_name='picklist', index=models.Index( - fields=['tableName'], + fields=['tablename'], name='picklist_tablename_idx', ), ), - # PreparationProperty - migrations.AddIndex( - model_name='preparationproperty', - index=models.Index( - fields=['guid'], - name='preparationprop_guid_idx', - ), - ), - - # PrepType + # Preptype migrations.AddIndex( model_name='preptype', index=models.Index( @@ -392,141 +322,111 @@ class Migration(migrations.Migration): ), ), - # ReferenceWork + # Preparationproperty migrations.AddIndex( - model_name='referencework', + model_name='preparationproperty', index=models.Index( - fields=['libraryNumber'], - name='referencework_librarynumber_idx', + fields=['guid'], + name='preparationprop_guid_idx', ), ), - # RelativeAge + # Referencework migrations.AddIndex( - model_name='relativeage', + model_name='referencework', index=models.Index( - fields=['verbatimName'], - name='relativeage_verbatimname_idx', + fields=['librarynumber'], + name='referencework_librarynumber_idx', ), ), - # SpAuditLogField + # Spauditlogfield migrations.AddIndex( model_name='spauditlogfield', index=models.Index( - fields=['fieldName'], + fields=['fieldname'], name='spauditlogfield_fieldname_idx', ), ), - # Spdataset (again; same as above, safe but redundant if both kept) - # (Kept single index definition above.) - - # SpecifyUser - migrations.AddIndex( - model_name='specifyuser', - index=models.Index( - fields=['name'], - name='specifyuser_name_idx', - ), - ), - - # SpExportSchema + # Spexportschema migrations.AddIndex( model_name='spexportschema', index=models.Index( - fields=['schemaName'], + fields=['schemaname'], name='spexportschema_schemaname_idx', ), ), - # SpExportSchemaItem + # Spexportschemaitem migrations.AddIndex( model_name='spexportschemaitem', index=models.Index( - fields=['fieldName'], + fields=['fieldname'], name='spexpschemaitem_fieldname_idx', ), ), - # SpExportSchemaItemMapping + # Spexportschemaitemmapping migrations.AddIndex( model_name='spexportschemaitemmapping', index=models.Index( - fields=['exportedFieldName'], + fields=['exportedfieldname'], name='spexpschemaitemmap_expfield_idx', ), ), - # SpExportSchemaMapping + # Spexportschemamapping migrations.AddIndex( model_name='spexportschemamapping', index=models.Index( - fields=['mappingName'], + fields=['mappingname'], name='spexpschemamap_mappingname_idx', ), ), - # SpFieldValueDefault + # Spfieldvaluedefault migrations.AddIndex( model_name='spfieldvaluedefault', index=models.Index( - fields=['fieldName'], + fields=['fieldname'], name='spfieldvaluedef_fieldname_idx', ), ), migrations.AddIndex( model_name='spfieldvaluedefault', index=models.Index( - fields=['tableName'], + fields=['tablename'], name='spfieldvaluedef_tablename_idx', ), ), - # LibraryRole (splibraryrole) - migrations.AddIndex( - model_name='libraryrole', - index=models.Index( - fields=['name'], - name='libraryrole_name_idx', - ), - ), - - # SpLocaleContainer + # Splocalecontainer migrations.AddIndex( model_name='splocalecontainer', index=models.Index( - fields=['pickListName'], + fields=['picklistname'], name='splocalecont_picklistname_idx', ), ), - # SpLocaleContainerItem + # Splocalecontaineritem migrations.AddIndex( model_name='splocalecontaineritem', index=models.Index( - fields=['pickListName'], + fields=['picklistname'], name='splocalecontitem_picklist_idx', ), ), migrations.AddIndex( model_name='splocalecontaineritem', index=models.Index( - fields=['webLinkName'], + fields=['weblinkname'], name='splocalecontitem_weblink_idx', ), ), - # SpMerging - migrations.AddIndex( - model_name='spmerging', - index=models.Index( - fields=['name'], - name='spmerging_name_idx', - ), - ), - - # SpPermission + # Sppermission migrations.AddIndex( model_name='sppermission', index=models.Index( @@ -535,7 +435,7 @@ class Migration(migrations.Migration): ), ), - # SpPrincipal + # Spprincipal migrations.AddIndex( model_name='spprincipal', index=models.Index( @@ -544,46 +444,46 @@ class Migration(migrations.Migration): ), ), - # SpQuery + # Spquery migrations.AddIndex( model_name='spquery', index=models.Index( - fields=['contextName'], + fields=['contextname'], name='spquery_contextname_idx', ), ), - # SpQueryField + # Spqueryfield migrations.AddIndex( model_name='spqueryfield', index=models.Index( - fields=['fieldName'], + fields=['fieldname'], name='spqueryfield_fieldname_idx', ), ), migrations.AddIndex( model_name='spqueryfield', index=models.Index( - fields=['formatName'], + fields=['formatname'], name='spqueryfield_formatname_idx', ), ), - # Role (sprole) + # Spviewsetobj migrations.AddIndex( - model_name='role', + model_name='spviewsetobj', index=models.Index( - fields=['name'], - name='role_name_idx', + fields=['filename'], + name='spviewsetobj_filename_idx', ), ), - # SpViewSetObj + # Specifyuser migrations.AddIndex( - model_name='spviewsetobj', + model_name='specifyuser', index=models.Index( - fields=['fileName'], - name='spviewsetobj_filename_idx', + fields=['name'], + name='specifyuser_name_idx', ), ), @@ -591,19 +491,19 @@ class Migration(migrations.Migration): migrations.AddIndex( model_name='storage', index=models.Index( - fields=['highestChildNodeNumber'], + fields=['highestchildnodenumber'], name='storage_highchildnodenumb_idx', ), ), migrations.AddIndex( model_name='storage', index=models.Index( - fields=['nodeNumber'], + fields=['nodenumber'], name='storage_nodenumber_idx', ), ), - # StorageTreeDef + # Storagetreedef migrations.AddIndex( model_name='storagetreedef', index=models.Index( @@ -612,7 +512,7 @@ class Migration(migrations.Migration): ), ), - # StorageTreeDefItem + # Storagetreedefitem migrations.AddIndex( model_name='storagetreedefitem', index=models.Index( @@ -625,33 +525,33 @@ class Migration(migrations.Migration): migrations.AddIndex( model_name='taxon', index=models.Index( - fields=['cultivarName'], + fields=['cultivarname'], name='taxon_cultivarname_idx', ), ), migrations.AddIndex( model_name='taxon', index=models.Index( - fields=['groupNumber'], + fields=['groupnumber'], name='taxon_groupnumber_idx', ), ), migrations.AddIndex( model_name='taxon', index=models.Index( - fields=['highestChildNodeNumber'], + fields=['highestchildnodenumber'], name='taxon_highchildnodenumb_idx', ), ), migrations.AddIndex( model_name='taxon', index=models.Index( - fields=['nodeNumber'], + fields=['nodenumber'], name='taxon_nodenumber_idx', ), ), - # TaxonTreeDef + # Taxontreedef migrations.AddIndex( model_name='taxontreedef', index=models.Index( @@ -660,7 +560,7 @@ class Migration(migrations.Migration): ), ), - # TaxonTreeDefItem + # Taxontreedefitem migrations.AddIndex( model_name='taxontreedefitem', index=models.Index( @@ -669,44 +569,50 @@ class Migration(migrations.Migration): ), ), - # TectonicUnit + # Voucherrelationship migrations.AddIndex( - model_name='tectonicunit', + model_name='voucherrelationship', index=models.Index( - fields=['fullName'], - name='tectonicunit_fullname_idx', + fields=['vouchernumber'], + name='voucherrel_vouchernumber_idx', ), ), + + # Collectionobjecttype migrations.AddIndex( - model_name='tectonicunit', + model_name='collectionobjecttype', index=models.Index( - fields=['guid'], - name='tectonicunit_guid_idx', + fields=['name'], + name='collectionobjecttype_name_idx', ), ), + + # Collectionobjectgrouptype migrations.AddIndex( - model_name='tectonicunit', + model_name='collectionobjectgrouptype', index=models.Index( - fields=['highestChildNodeNumber'], - name='tectonicunit_highchildnumb_idx', + fields=['name'], + name='collectionobjectgroupt_name_idx', ), ), + + # Collectionobjectgroup migrations.AddIndex( - model_name='tectonicunit', + model_name='collectionobjectgroup', index=models.Index( - fields=['name'], - name='tectonicunit_name_idx', + fields=['guid'], + name='collectionobjectgroup_guid_idx', ), ), migrations.AddIndex( - model_name='tectonicunit', + model_name='collectionobjectgroup', index=models.Index( - fields=['nodeNumber'], - name='tectonicunit_nodenumber_idx', + fields=['name'], + name='collectionobjectgroup_name_idx', ), ), - # TectonicUnitTreeDef + # Tectonicunittreedef migrations.AddIndex( model_name='tectonicunittreedef', index=models.Index( @@ -715,7 +621,7 @@ class Migration(migrations.Migration): ), ), - # TectonicUnitTreeDefItem + # Tectonicunittreedefitem migrations.AddIndex( model_name='tectonicunittreedefitem', index=models.Index( @@ -724,12 +630,40 @@ class Migration(migrations.Migration): ), ), - # VoucherRelationship + # Tectonicunit migrations.AddIndex( - model_name='voucherrelationship', + model_name='tectonicunit', index=models.Index( - fields=['voucherNumber'], - name='voucherrel_vouchernumber_idx', + fields=['fullname'], + name='tectonicunit_fullname_idx', ), ), - ] \ No newline at end of file + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['guid'], + name='tectonicunit_guid_idx', + ), + ), + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['highestchildnodenumber'], + name='tectonicunit_highchildnumb_idx', + ), + ), + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['name'], + name='tectonicunit_name_idx', + ), + ), + migrations.AddIndex( + model_name='tectonicunit', + index=models.Index( + fields=['nodenumber'], + name='tectonicunit_nodenumber_idx', + ), + ), + ] diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index 92385d8d800..585722ea9fb 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -584,7 +584,6 @@ class Meta: models.Index(fields=['scopeid'], name='AttchScopeIDIDX'), models.Index(fields=['scopetype'], name='AttchScopeTypeIDX'), models.Index(fields=['guid'], name='AttchmentGuidIDX'), - models.Index(fields=['origfilename'], name='attachment_origfilename_idx'), ] @@ -7971,9 +7970,6 @@ class Relativeage(models.Model): class Meta: db_table = 'relativeage' ordering = () - indexes = [ - models.Index(fields=['verbatimname'], name='relativeage_verbatimname_idx'), - ] save = partialmethod(custom_save) From 89cdcb70e6cf8a126ad1fbd247fbc6becac8d230 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 13 Mar 2026 13:10:11 -0500 Subject: [PATCH 05/11] Fix index name length errors --- .../specify/migrations/0045_add_indexes.py | 18 +++++++++--------- specifyweb/specify/models.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/specifyweb/specify/migrations/0045_add_indexes.py b/specifyweb/specify/migrations/0045_add_indexes.py index 01157df997e..43715ed0b40 100644 --- a/specifyweb/specify/migrations/0045_add_indexes.py +++ b/specifyweb/specify/migrations/0045_add_indexes.py @@ -21,7 +21,7 @@ class Migration(migrations.Migration): model_name='agentidentifier', index=models.Index( fields=['identifiertype'], - name='agentidentifier_identifiertype_idx', + name='agid_identifiertype_idx', ), ), @@ -37,7 +37,7 @@ class Migration(migrations.Migration): model_name='agentspecialty', index=models.Index( fields=['specialtyname'], - name='agentspecialty_specialtyname_idx', + name='agsp_specialtyname_idx', ), ), @@ -80,7 +80,7 @@ class Migration(migrations.Migration): model_name='collectionobject', index=models.Index( fields=['projectnumber'], - name='collectionobject_projectnumber_idx', + name='colobj_projectnumber_idx', ), ), @@ -107,7 +107,7 @@ class Migration(migrations.Migration): model_name='exchangein', index=models.Index( fields=['exchangeinnumber'], - name='exchangein_exchangeinnumber_idx', + name='exchin_exchinnum_idx', ), ), @@ -139,7 +139,7 @@ class Migration(migrations.Migration): model_name='geography', index=models.Index( fields=['highestchildnodenumber'], - name='geography_highchildnodenumb_idx', + name='geography_hchnodenum_idx', ), ), migrations.AddIndex( @@ -225,7 +225,7 @@ class Migration(migrations.Migration): model_name='lithostrat', index=models.Index( fields=['highestchildnodenumber'], - name='lithostrat_highchildnodenumb_idx', + name='lithostrat_hchnode_idx', ), ), migrations.AddIndex( @@ -336,7 +336,7 @@ class Migration(migrations.Migration): model_name='referencework', index=models.Index( fields=['librarynumber'], - name='referencework_librarynumber_idx', + name='refwork_librarynum_idx', ), ), @@ -372,7 +372,7 @@ class Migration(migrations.Migration): model_name='spexportschemaitemmapping', index=models.Index( fields=['exportedfieldname'], - name='spexpschemaitemmap_expfield_idx', + name='spexpitemmap_expfld_idx', ), ), @@ -592,7 +592,7 @@ class Migration(migrations.Migration): model_name='collectionobjectgrouptype', index=models.Index( fields=['name'], - name='collectionobjectgroupt_name_idx', + name='colobjgrouptype_name_idx', ), ), diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index 585722ea9fb..3560232bb95 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -433,7 +433,7 @@ class Meta: ordering = () indexes = [ models.Index(fields=['identifier'], name='agentidentifier_identifier_idx'), - models.Index(fields=['identifiertype'], name='agentidentifier_identifiertype_idx'), + models.Index(fields=['identifiertype'], name='agid_identifiertype_idx'), ] @@ -463,7 +463,7 @@ class Meta: unique_together = (('agent', 'ordernumber'),) indexes = [ models.Index(fields=['ordernumber'], name='agentspecialty_ordernumber_idx'), - models.Index(fields=['specialtyname'], name='agentspecialty_specialtyname_idx'), + models.Index(fields=['specialtyname'], name='agsp_specialtyname_idx'), ] @@ -1529,7 +1529,7 @@ class Meta: models.Index(fields=['guid'], name='ColObjGuidIDX'), models.Index(fields=['collectionmemberid'], name='COColMemIDX'), models.Index(fields=['name'], name='collectionobject_name_idx'), - models.Index(fields=['projectnumber'], name='collectionobject_projectnumber_idx'), + models.Index(fields=['projectnumber'], name='colobj_projectnumber_idx'), ] @@ -3140,7 +3140,7 @@ class Meta: indexes = [ models.Index(fields=['exchangedate'], name='ExchangeDateIDX'), models.Index(fields=['descriptionofmaterial'], name='DescriptionOfMaterialIDX'), - models.Index(fields=['exchangeinnumber'], name='exchangein_exchangeinnumber_idx'), + models.Index(fields=['exchangeinnumber'], name='exchin_exchinnum_idx'), ] @@ -3718,7 +3718,7 @@ class Meta: models.Index(fields=['fullname'], name='GeoFullNameIDX'), models.Index(fields=['commonname'], name='geography_commonname_idx'), models.Index(fields=['guid'], name='geography_guid_idx'), - models.Index(fields=['highestchildnodenumber'], name='geography_highchildnodenumb_idx'), + models.Index(fields=['highestchildnodenumber'], name='geography_hchnodenum_idx'), models.Index(fields=['nodenumber'], name='geography_nodenumber_idx'), ] @@ -4343,7 +4343,7 @@ class Meta: models.Index(fields=['name'], name='LithoNameIDX'), models.Index(fields=['fullname'], name='LithoFullNameIDX'), models.Index(fields=['guid'], name='LithoGuidIDX'), - models.Index(fields=['highestchildnodenumber'], name='lithostrat_highchildnodenumb_idx'), + models.Index(fields=['highestchildnodenumber'], name='lithostrat_hchnode_idx'), models.Index(fields=['nodenumber'], name='lithostrat_nodenumber_idx'), ] @@ -5796,7 +5796,7 @@ class Meta: models.Index(fields=['publisher'], name='RefWrkPublisherIDX'), models.Index(fields=['guid'], name='RefWrkGuidIDX'), models.Index(fields=['isbn'], name='ISBNIDX'), - models.Index(fields=['librarynumber'], name='referencework_librarynumber_idx'), + models.Index(fields=['librarynumber'], name='refwork_librarynum_idx'), ] @@ -6221,7 +6221,7 @@ class Meta: db_table = 'spexportschemaitemmapping' ordering = () indexes = [ - models.Index(fields=['exportedfieldname'], name='spexpschemaitemmap_expfield_idx'), + models.Index(fields=['exportedfieldname'], name='spexpitemmap_expfld_idx'), ] @@ -7802,7 +7802,7 @@ class Meta: db_table = 'collectionobjectgrouptype' ordering = () indexes = [ - models.Index(fields=['name'], name='collectionobjectgroupt_name_idx'), + models.Index(fields=['name'], name='colobjgrouptype_name_idx'), ] save = partialmethod(custom_save) From 7e750f864d78c36c85dc61c3a23c25855b808740 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 13 Mar 2026 13:57:03 -0500 Subject: [PATCH 06/11] Rewrite get_tree_rows to avoid api timeout --- specifyweb/backend/trees/views.py | 50 ++++++++++++++----------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/specifyweb/backend/trees/views.py b/specifyweb/backend/trees/views.py index f636c869944..303c4edcf72 100644 --- a/specifyweb/backend/trees/views.py +++ b/specifyweb/backend/trees/views.py @@ -182,44 +182,38 @@ def get_tree_rows(treedef, tree, parentid, sortfield, include_author, session): treedef_col = getattr(node, tree_table.name + "TreeDefID") orderby = getattr(node, tree_table.get_field_strict(sortfield).name) + child_count = ( + select(func.count(child._id)) + .where(child.ParentID == node._id) + .correlate(node) + .scalar_subquery() + ) + synonyms = ( + select(group_concat(distinct(synonym.fullName), separator=", ")) + .where(synonym.AcceptedID == node._id) + .correlate(node) + .scalar_subquery() + ) - # We use min for grouped columns because for some reason, SQL is rejecting - # the group_by in some dbs due to "only_full_group_by". It is somehow not - # smart enough to see that there is no dependency in the columns going from - # main table to the to-manys (child, and syns). - # I want to use ANY_VALUE() but that's not supported by MySQL 5.6- and MariaDB. - # I don't want to disable "only_full_group_by" in case someone misuses it... - # applying min to fool into thinking it is aggregated. - # these values are guarenteed to be the same cols = [ node._id.label("id"), - func.min(node.name).label("name"), - func.min(node.fullName).label("full_name"), - func.min(node.nodeNumber).label("node_number"), - func.min(node.highestChildNodeNumber).label("highest_child_number"), - func.min(node.rankId).label("rank_id"), - - func.min(node.AcceptedID).label("accepted_id"), - func.min(accepted.fullName).label("accepted_fullname"), - - ( - func.min(node.author) - if include_author - else func.min(literal("NULL")) - ).label("author"), - - func.count(distinct(child._id)).label("child_count"), - group_concat(distinct(synonym.fullName), separator=", ").label("synonyms"), + node.name.label("name"), + node.fullName.label("full_name"), + node.nodeNumber.label("node_number"), + node.highestChildNodeNumber.label("highest_child_number"), + node.rankId.label("rank_id"), + node.AcceptedID.label("accepted_id"), + accepted.fullName.label("accepted_fullname"), + (node.author if include_author else literal("NULL")).label("author"), + child_count.label("child_count"), + synonyms.label("synonyms"), ] query = ( select(*cols) - .outerjoin(child, child.ParentID == node._id) .outerjoin(accepted, node.AcceptedID == accepted._id) - .outerjoin(synonym, synonym.AcceptedID == node._id) .where(treedef_col == int(treedef)) .where(node.ParentID == parentid) - .group_by(node._id) .order_by(orderby) ) From d483603f451225e6f02f97db5bf7f777f2290820 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 13 Mar 2026 14:15:18 -0500 Subject: [PATCH 07/11] Fix subqueries in get_tree_rows --- specifyweb/backend/trees/views.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/specifyweb/backend/trees/views.py b/specifyweb/backend/trees/views.py index 303c4edcf72..1c42e533f11 100644 --- a/specifyweb/backend/trees/views.py +++ b/specifyweb/backend/trees/views.py @@ -182,17 +182,22 @@ def get_tree_rows(treedef, tree, parentid, sortfield, include_author, session): treedef_col = getattr(node, tree_table.name + "TreeDefID") orderby = getattr(node, tree_table.get_field_strict(sortfield).name) - child_count = ( - select(func.count(child._id)) - .where(child.ParentID == node._id) - .correlate(node) - .scalar_subquery() + child_counts = ( + select( + child.ParentID.label("parent_id"), + func.count(child._id).label("child_count"), + ) + .group_by(child.ParentID) + .subquery() ) - synonyms = ( - select(group_concat(distinct(synonym.fullName), separator=", ")) - .where(synonym.AcceptedID == node._id) - .correlate(node) - .scalar_subquery() + synonym_names = ( + select( + synonym.AcceptedID.label("accepted_id"), + group_concat(distinct(synonym.fullName), separator=", ").label("synonyms"), + ) + .where(synonym.AcceptedID.is_not(None)) + .group_by(synonym.AcceptedID) + .subquery() ) cols = [ @@ -205,13 +210,15 @@ def get_tree_rows(treedef, tree, parentid, sortfield, include_author, session): node.AcceptedID.label("accepted_id"), accepted.fullName.label("accepted_fullname"), (node.author if include_author else literal("NULL")).label("author"), - child_count.label("child_count"), - synonyms.label("synonyms"), + func.coalesce(child_counts.c.child_count, 0).label("child_count"), + synonym_names.c.synonyms.label("synonyms"), ] query = ( select(*cols) .outerjoin(accepted, node.AcceptedID == accepted._id) + .outerjoin(child_counts, child_counts.c.parent_id == node._id) + .outerjoin(synonym_names, synonym_names.c.accepted_id == node._id) .where(treedef_col == int(treedef)) .where(node.ParentID == parentid) .order_by(orderby) From 120b1909c041842a807ebf3950f9e42aaf38f392 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Fri, 13 Mar 2026 14:29:01 -0500 Subject: [PATCH 08/11] Add test_taxon_rows_include_author_and_synonyms unit test --- specifyweb/backend/trees/tests/test_trees.py | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/specifyweb/backend/trees/tests/test_trees.py b/specifyweb/backend/trees/tests/test_trees.py index a3acd477518..2c6628d068f 100644 --- a/specifyweb/backend/trees/tests/test_trees.py +++ b/specifyweb/backend/trees/tests/test_trees.py @@ -455,6 +455,77 @@ def _run_for_row(): ] self.assertCountEqual(results, expected) + def test_taxon_rows_include_author_and_synonyms(self): + root = self.make_taxontree( + "Life", + "Taxonomy Root", + definition=self.taxontreedef, + ) + animalia = self.make_taxontree( + "Animalia", + "Kingdom", + definition=self.taxontreedef, + parent=root, + author="L.", + ) + metazoa = self.make_taxontree( + "Metazoa", + "Kingdom", + definition=self.taxontreedef, + parent=root, + acceptedtaxon=animalia, + fullname="Metazoa", + author="Haeckel", + ) + animalia.refresh_from_db() + metazoa.refresh_from_db() + + @contextmanager + def _run_for_row(): + with TreeViewsTest.test_session_context() as session: + set_group_concat_max_len(connection.cursor()) + yield session + + with _run_for_row() as session: + results = get_tree_rows( + self.taxontreedef.id, + "Taxon", + root.id, + "name", + True, + session, + ) + expected = [ + ( + animalia.id, + animalia.name, + animalia.fullname, + animalia.nodenumber, + animalia.highestchildnodenumber, + animalia.rankid, + None, + None, + animalia.author, + 0, + metazoa.fullname, + ), + ( + metazoa.id, + metazoa.name, + metazoa.fullname, + metazoa.nodenumber, + metazoa.highestchildnodenumber, + metazoa.rankid, + animalia.id, + animalia.fullname, + metazoa.author, + 0, + None, + ), + ] + + self.assertCountEqual(results, expected) + class AddDeleteRankResourcesTest(ApiTests): def test_add_ranks_without_defaults(self): From 6e7d6521d963d30219b2fcd65deaeeae4f1cb745 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Wed, 18 Mar 2026 15:47:40 +0000 Subject: [PATCH 09/11] Lint code with ESLint and Prettier Triggered by f883c1c5e99af2729d49c280d531ed1924632d28 on branch refs/heads/issue-7482 --- .../__tests__/AppResourceEditButton.test.tsx | 24 +++++++++---------- .../js_src/lib/components/Molecules/index.tsx | 6 ++--- .../WbPlanView/CustomSelectElement.tsx | 2 +- .../js_src/lib/components/WorkBench/hooks.ts | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/AppResources/__tests__/AppResourceEditButton.test.tsx b/specifyweb/frontend/js_src/lib/components/AppResources/__tests__/AppResourceEditButton.test.tsx index 76e1b3e8977..c8aff34b80e 100644 --- a/specifyweb/frontend/js_src/lib/components/AppResources/__tests__/AppResourceEditButton.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/AppResources/__tests__/AppResourceEditButton.test.tsx @@ -70,18 +70,18 @@ describe('AppResourceEditButton', () => { const dialog = getByRole('dialog'); expect(dialog.innerHTML).toMatchInlineSnapshot(` -"

TestTitle

" -`); + "

TestTitle

" + `); expect(handleDeleted).not.toHaveBeenCalled(); }); diff --git a/specifyweb/frontend/js_src/lib/components/Molecules/index.tsx b/specifyweb/frontend/js_src/lib/components/Molecules/index.tsx index 0adade45a00..6420a8f4c10 100644 --- a/specifyweb/frontend/js_src/lib/components/Molecules/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/Molecules/index.tsx @@ -7,9 +7,9 @@ import React from 'react'; +import { useHueDifference } from '../../hooks/useHueDifference'; import { commonText } from '../../localization/common'; import type { RA } from '../../utils/types'; -import { useHueDifference } from '../../hooks/useHueDifference'; export const loadingGif = (
@@ -31,7 +31,7 @@ export const loadingGif = ( * This must be accompanied by a label since loading bar is hidden from screen * readers */ -const LoadingBar = () => { +function LoadingBar() { const hueDifference = useHueDifference(); return ( @@ -45,7 +45,7 @@ const LoadingBar = () => { />
); -}; +} export const loadingBar = ; diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/CustomSelectElement.tsx b/specifyweb/frontend/js_src/lib/components/WbPlanView/CustomSelectElement.tsx index bcdd53e4ddf..f3e4a7aa0b1 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/CustomSelectElement.tsx +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/CustomSelectElement.tsx @@ -849,8 +849,8 @@ export function CustomSelectElement({ > Date: Thu, 19 Mar 2026 14:00:47 -0500 Subject: [PATCH 10/11] handle MultipleObjectsReturned --- specifyweb/backend/stored_queries/format.py | 29 +++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/specifyweb/backend/stored_queries/format.py b/specifyweb/backend/stored_queries/format.py index 079a9674db6..03985055b78 100644 --- a/specifyweb/backend/stored_queries/format.py +++ b/specifyweb/backend/stored_queries/format.py @@ -89,16 +89,29 @@ def lookup_name(name: str) -> Element | None: return None def getFormatterFromSchema() -> Element: - - try: - formatter_name = Splocalecontainer.objects.get( - name=specify_model.name.lower(), - schematype=0, - discipline=self.collection.discipline - ).format - except Splocalecontainer.DoesNotExist: + containers = Splocalecontainer.objects.filter( + name=specify_model.name.lower(), + schematype=0, + discipline=self.collection.discipline, + ).order_by('-timestampmodified', '-id') + container_count = containers.count() + if container_count == 0: return None + formatter_name = ( + containers.exclude(format__isnull=True) + .exclude(format='') + .values_list('format', flat=True) + .first() + ) + if container_count > 1: + logger.warning( + "Multiple Splocalecontainer rows found for %s in discipline %s using formatter %r", + specify_model.name.lower(), + self.collection.discipline_id, + formatter_name, + ) + if formatter_name: return lookup_name(formatter_name) else: From 39cb9c001a84df9dabce037555b566110975016d Mon Sep 17 00:00:00 2001 From: Grant Fitzsimmons <37256050+grantfitzsimmons@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:01:45 +0000 Subject: [PATCH 11/11] Lint code with ESLint and Prettier Triggered by 0f0f17afa60a6bf4dab47d5600a0fca8495eaaef on branch refs/heads/issue-7482 --- .../lib/components/Notifications/NotificationRenderers.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx index 26f4696c407..790b3373284 100644 --- a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx +++ b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx @@ -395,9 +395,7 @@ export const notificationRenderers: IR< ); }, 'collection-creation-starting'() { - return ( -

{setupToolText.collectionCreationStarted()}

- ); + return

{setupToolText.collectionCreationStarted()}

; }, default(notification) { console.error('Unknown notification type', { notification });