diff --git a/specifyweb/backend/locality_update_tool/tests/test_localityupdate_status.py b/specifyweb/backend/locality_update_tool/tests/test_localityupdate_status.py index acad98e50a6..5b6fceb161f 100644 --- a/specifyweb/backend/locality_update_tool/tests/test_localityupdate_status.py +++ b/specifyweb/backend/locality_update_tool/tests/test_localityupdate_status.py @@ -36,7 +36,7 @@ def test_localityupdate_not_exist(self): self._assertStatusCodeEqual(response, http.HttpResponseNotFound.status_code) self.assertEqual(response.content.decode(), f"The localityupdate with task id '{task_id}' was not found") - @patch("specifyweb.specify.views.update_locality_task.AsyncResult") + @patch("specifyweb.backend.locality_update_tool.views.update_locality_task.AsyncResult") def test_failed(self, AsyncResult: Mock): mock_result = Mock() mock_result.state = CELERY_TASK_STATE.FAILURE @@ -70,7 +70,7 @@ def test_failed(self, AsyncResult: Mock): } ) - @patch("specifyweb.specify.views.update_locality_task.AsyncResult") + @patch("specifyweb.backend.locality_update_tool.views.update_locality_task.AsyncResult") def test_parse_failed(self, AsyncResult: Mock): mock_result = Mock() mock_result.state = CELERY_TASK_STATE.SUCCESS @@ -98,7 +98,7 @@ def test_parse_failed(self, AsyncResult: Mock): } ) - @patch("specifyweb.specify.views.update_locality_task.AsyncResult") + @patch("specifyweb.backend.locality_update_tool.views.update_locality_task.AsyncResult") def test_parsed(self, AsyncResult: Mock): mock_result = Mock() mock_result.state = CELERY_TASK_STATE.SUCCESS @@ -149,7 +149,7 @@ def test_parsed(self, AsyncResult: Mock): } ) - @patch("specifyweb.specify.views.update_locality_task.AsyncResult") + @patch("specifyweb.backend.locality_update_tool.views.update_locality_task.AsyncResult") def test_succeeded(self, AsyncResult: Mock): mock_result = Mock() mock_result.state = LocalityUpdateStatus.SUCCEEDED @@ -181,7 +181,7 @@ def test_succeeded(self, AsyncResult: Mock): } ) - @patch("specifyweb.specify.views.update_locality_task.AsyncResult") + @patch("specifyweb.backend.locality_update_tool.views.update_locality_task.AsyncResult") def test_succeeded_locality_rows(self, AsyncResult: Mock): mock_result = Mock() mock_result.state = LocalityUpdateStatus.SUCCEEDED diff --git a/specifyweb/backend/locality_update_tool/tests/test_parse_field.py b/specifyweb/backend/locality_update_tool/tests/test_parse_field.py index 02e7f447433..3e512bb41df 100644 --- a/specifyweb/backend/locality_update_tool/tests/test_parse_field.py +++ b/specifyweb/backend/locality_update_tool/tests/test_parse_field.py @@ -33,7 +33,7 @@ def test_no_ui_formatter(self): self.assertEqual(parsed_with_value, parsed_with_value_result) - @patch("specifyweb.specify.update_locality.get_uiformatter") + @patch("specifyweb.backend.locality_update_tool.update_locality.get_uiformatter") def test_cnn_formatter(self, get_uiformatter: Mock): get_uiformatter.return_value = UIFormatter( diff --git a/specifyweb/backend/locality_update_tool/update_locality.py b/specifyweb/backend/locality_update_tool/update_locality.py index ae1873f1631..1fe2985daf8 100644 --- a/specifyweb/backend/locality_update_tool/update_locality.py +++ b/specifyweb/backend/locality_update_tool/update_locality.py @@ -379,11 +379,19 @@ def parse_locality_set(collection, raw_headers: list[str], data: list[list[str]] locality_id: int | None = None if len( locality_query) != 1 else locality_query[0].id - parsed_locality_fields = [parse_field( - collection, 'Locality', dict['field'], dict['value'], locality_id, row_number) for dict in locality_values if dict['value'].strip() != ""] + parsed_locality_fields = [ + parse_field( + collection, 'Locality', d['field'], d['value'], locality_id, row_number + ) + for d in locality_values + ] - parsed_geocoorddetail_fields = [parse_field( - collection, 'Geocoorddetail', dict["field"], dict['value'], locality_id, row_number) for dict in geocoorddetail_values if dict['value'].strip() != ""] + parsed_geocoorddetail_fields = [ + parse_field( + collection, 'Geocoorddetail', d['field'], d['value'], locality_id, row_number + ) + for d in geocoorddetail_values + ] parsed_row, parsed_errors = merge_parse_results( [*parsed_locality_fields, *parsed_geocoorddetail_fields], locality_id, row_number) diff --git a/specifyweb/backend/stored_queries/build_models.py b/specifyweb/backend/stored_queries/build_models.py index 698282fd3a6..959e3d99458 100644 --- a/specifyweb/backend/stored_queries/build_models.py +++ b/specifyweb/backend/stored_queries/build_models.py @@ -141,4 +141,3 @@ def make_relationship(reldef): for tabledef in datamodel.tables: map_class(tabledef) - diff --git a/specifyweb/backend/stored_queries/tests/tests.py b/specifyweb/backend/stored_queries/tests/tests.py index 3f6b71e6d74..3bd4a98745c 100644 --- a/specifyweb/backend/stored_queries/tests/tests.py +++ b/specifyweb/backend/stored_queries/tests/tests.py @@ -305,4 +305,5 @@ def test_sqlalchemy_model_errors(self): "CollectionObjectGroup": { "incorrect_direction": {"cojo": ["onetomany", "onetoone"]} }, -} + "SgrBatchMatchResultSet": {"not_found": ["items"]}, +} \ No newline at end of file diff --git a/specifyweb/backend/stored_queries/tests/tests_legacy.py b/specifyweb/backend/stored_queries/tests/tests_legacy.py index cb34b27ae22..32f7a7ec0d8 100644 --- a/specifyweb/backend/stored_queries/tests/tests_legacy.py +++ b/specifyweb/backend/stored_queries/tests/tests_legacy.py @@ -956,10 +956,10 @@ def test_sqlalchemy_model_errors(self): }, "CollectionObjectGroup": { "incorrect_direction": { - "cojo": [ - "onetomany", - "onetoone" - ] + "cojo": ["onetomany", "onetoone"] } }, -} + "SgrBatchMatchResultSet": { + "not_found": ['items'] + } +} \ No newline at end of file diff --git a/specifyweb/backend/trees/extras.py b/specifyweb/backend/trees/extras.py index 52d16aec7d2..56414fa2826 100644 --- a/specifyweb/backend/trees/extras.py +++ b/specifyweb/backend/trees/extras.py @@ -411,18 +411,46 @@ def synonymize(node, into, agent, user=None, collection=None): # This check can be disabled by a remote pref import specifyweb.backend.context.app_resource as app_resource - collection_prefs_json, _, __ = app_resource.get_app_resource(collection, user, 'CollectionPreferences') - if collection_prefs_json is not None: - collection_prefs_dict = json.loads(collection_prefs_json) - treeManagement_pref = collection_prefs_dict.get('treeManagement', {}) + collection_prefs_dict = {} # always defined + + res = app_resource.get_app_resource(collection, user, 'CollectionPreferences') + force_checks = (collection is None or user is None) + if res is not None: + collection_prefs_json, _, __ = res + if collection_prefs_json: + try: + collection_prefs_dict = json.loads(collection_prefs_json) or {} + except Exception: + collection_prefs_dict = {} + treeManagement_pref = collection_prefs_dict.get('treeManagement', {}) + if force_checks and target.children.exists(): + raise TreeBusinessRuleException( + f'Synonymizing "{node.fullname}" to "{into.fullname}" which has children', + {"tree": "Taxon", + "localizationKey": "nodeSynonimizeWithChildren", + "node": { + "id": node.id, + "rankid": node.rankid, + "fullName": node.fullname, + "children": list(node.children.values('id', 'fullname')) + }, + "parent": { + "id": into.id, + "rankid": into.rankid, + "fullName": into.fullname, + "parentid": into.parent.id, + "children": list(into.children.values('id', 'fullname')) + }} + ) + force_checks = (collection is None or user is None) synonymized = treeManagement_pref.get('synonymized', {}) \ if isinstance(treeManagement_pref, dict) else {} add_synonym_enabled = synonymized.get(r'^sp7\.allow_adding_child_to_synonymized_parent\.' + node.specify_model.name + '=(.+)', False) if isinstance(synonymized, dict) else False - if node.children.count() > 0 and (add_synonym_enabled is True): + if node.children.count() > 0 and (force_checks or add_synonym_enabled is False): raise TreeBusinessRuleException( f'Synonymizing node "{node.fullname}" which has children', {"tree" : "Taxon", diff --git a/specifyweb/backend/workbench/upload/predicates.py b/specifyweb/backend/workbench/upload/predicates.py index 30b3c4be5bb..9a22d82ce87 100644 --- a/specifyweb/backend/workbench/upload/predicates.py +++ b/specifyweb/backend/workbench/upload/predicates.py @@ -12,7 +12,7 @@ import specifyweb.specify.models as spmodels from specifyweb.specify.utils.func import Func -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, FieldDoesNotExist, FieldError from specifyweb.backend.workbench.upload.clone import GENERIC_FIELDS_TO_SKIP @@ -39,6 +39,14 @@ def add_to_remove_node(previous: ToRemoveNode, new_node: ToRemoveNode) -> ToRemo **{key: [*previous.get(key, []), *values] for key, values in new_node.items()}, } +def _model_supports_filter_key(model, key: str) -> bool: + field_name = key[:-3] if key.endswith("_id") else key + try: + model._meta.get_field(field_name) + return True + except FieldDoesNotExist: + return False + class ToRemove(NamedTuple): model_name: str @@ -124,7 +132,7 @@ def _smart_apply( base_predicates = { _get_field_name(field_name): value for (field_name, value) in self.filters.items() - if not isinstance(value, list) + if not isinstance(value, list) and _model_supports_filter_key(current_model, field_name) } filtered = { @@ -134,9 +142,19 @@ def _smart_apply( unique_alias = next(get_unique_alias) + # Apply filters first + query = query.filter(**filtered) + + # IMPORTANT: downstream reduction logic assumes every predicate level + # defines a "predicate-N" alias, so always alias the PK. + unique_alias = next(get_unique_alias) alias_path = _get_field_name("id") - query = query.filter(**filtered).alias(**{unique_alias: F(alias_path)}) - aliases = [*aliases, (alias_path, unique_alias)] + try: + query = query.alias(**{unique_alias: F(alias_path)}) + aliases = [*aliases, (alias_path, unique_alias)] + except FieldError: + # Extremely defensive; every model should have "id" + pass def _reduce_by_key(rel_name: str): # mypy isn't able to infer types correctly @@ -284,12 +302,31 @@ def canonicalize_remove_node(node: ToRemoveNode) -> Q: def _map_matchee(matchee: list[ToRemoveMatchee], model_name: str) -> Exists: model: Model = get_model(model_name) - qs = [Q(**match["filter_on"]) for match in matchee] + + # Filter out any filter keys that don't exist on this model + qs: list[Q] = [] + for match in matchee: + safe_filter_on = { + k: v + for k, v in match["filter_on"].items() + if _model_supports_filter_key(model, k) + } + # If nothing remains, this particular matchee can't apply to this model + if safe_filter_on: + qs.append(Q(**safe_filter_on)) + + # If none of the matchees had any applicable filter keys, + # make this Exists() always false by filtering on an empty pk set. + if not qs: + return Exists(model.objects.none()) + qs_or = Func.make_ors(qs) query = model.objects.filter(qs_or) + to_remove = [match["remove"] for match in matchee if match["remove"] is not None] if to_remove: query = query.exclude(Func.make_ors(to_remove)) + return Exists(query) diff --git a/specifyweb/backend/workbench/upload/scoping.py b/specifyweb/backend/workbench/upload/scoping.py index cb91f468c84..d5a427bc374 100644 --- a/specifyweb/backend/workbench/upload/scoping.py +++ b/specifyweb/backend/workbench/upload/scoping.py @@ -67,6 +67,9 @@ def scoping_relationships(collection, table: Table) -> dict[str, int]: try: table.get_relationship("collection") extra_static["collection_id"] = collection.id + extra_static["discipline_id"] = collection.discipline_id + extra_static["division_id"] = collection.discipline.division_id + extra_static["institution_id"] = collection.discipline.division.institution_id except DoesNotExistError: pass diff --git a/specifyweb/backend/workbench/upload/upload_table.py b/specifyweb/backend/workbench/upload/upload_table.py index 4493015486a..25b69e6a0a8 100644 --- a/specifyweb/backend/workbench/upload/upload_table.py +++ b/specifyweb/backend/workbench/upload/upload_table.py @@ -791,7 +791,9 @@ def _do_clone(self, attrs) -> Any: def _get_inserter(self): def _inserter(model, attrs): - uploaded = model.objects.create(**attrs) + valid_fields = {f.attname for f in model._meta.concrete_fields} + filtered_attrs = {k: v for k, v in attrs.items() if k in valid_fields} + uploaded = model.objects.create(**filtered_attrs) self.auditor.insert(uploaded, None) return uploaded @@ -1146,7 +1148,11 @@ def is_equal(old, new): return old == new return { - key: FieldChangeInfo(field_name=key, old_value=getattr(reference_record, key), new_value=new_value) # type: ignore + key: FieldChangeInfo( + field_name=key, + old_value=getattr(reference_record, key), + new_value=new_value, + ) # type: ignore for (key, new_value) in attrs.items() - if not is_equal(getattr(reference_record, key), new_value) + if hasattr(reference_record, key) and not is_equal(getattr(reference_record, key), new_value) } diff --git a/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx b/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx index 0199a185191..c08a1e144b9 100644 --- a/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx +++ b/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx @@ -80,7 +80,8 @@ export function InteractionDialog({ ); const isLoanReturnLike = - isLoanReturn || (actionTable.name !== 'Loan' && actionTable.name.includes('Loan')); + isLoanReturn || + (actionTable.name !== 'Loan' && actionTable.name.includes('Loan')); const itemTable = isLoanReturnLike ? tables.Loan : tables.CollectionObject; @@ -206,8 +207,7 @@ export function InteractionDialog({ ) ).then((data) => availablePrepsReady(catalogNumbers, data, { - skipEntryMatch: - searchField.name.toLowerCase() !== 'catalognumber', + skipEntryMatch: searchField.name.toLowerCase() !== 'catalognumber', }) ) ); @@ -377,7 +377,9 @@ export function InteractionDialog({ values, isLoan ) - ).then((data) => availablePrepsReady(values, data, { skipEntryMatch: true })) + ).then((data) => + availablePrepsReady(values, data, { skipEntryMatch: true }) + ) ); } diff --git a/specifyweb/specify/datamodel.py b/specifyweb/specify/datamodel.py index 9fcbcdcb2b6..61c73b59ba3 100644 --- a/specifyweb/specify/datamodel.py +++ b/specifyweb/specify/datamodel.py @@ -351,6 +351,7 @@ def is_tree_table(table: Table): Relationship(name='specifyUser', type='many-to-one',required=False, relatedModelName='SpecifyUser', column='SpecifyUserID', otherSideName='agents'), Relationship(name='variants', type='one-to-many',required=False, relatedModelName='AgentVariant', otherSideName='agent', dependent=True), Relationship(name='components', type='one-to-many',required=False, relatedModelName='Component', otherSideName='identifiedBy'), + # Relationship(name='institutiontc', type='many-to-one', required=False, relatedModelName='InstitutionNetwork', column='InstitutionTCID', otherSideName='agents_institutiontc'), ], fieldAliases=[ @@ -3047,7 +3048,8 @@ def is_tree_table(table: Table): Field(name='timestampCreated', column='TimestampCreated', indexed=False, unique=False, required=True, type='java.sql.Timestamp'), Field(name='timestampModified', column='TimestampModified', indexed=False, unique=False, required=False, type='java.sql.Timestamp'), Field(name='type', column='Type', indexed=False, unique=False, required=False, type='java.lang.String', length=64), - Field(name='version', column='Version', indexed=False, unique=False, required=False, type='java.lang.Integer') + Field(name='version', column='Version', indexed=False, unique=False, required=False, type='java.lang.Integer'), + # Field(name='disciplineId', column='DisciplineId', indexed=False, unique=False, required=False, type='java.lang.Integer') ], indexes=[ Index(name='DisciplineNameIDX', column_names=['Name']) @@ -8824,6 +8826,322 @@ def is_tree_table(table: Table): ], ), + # Table( + # classname='edu.ku.brc.specify.datamodel.AutoNumSchColl', + # table='autonumsch_coll', + # tableId=1030, + # idColumns=['CollectionID', 'AutoNumberingSchemeID'], + # idFieldNames=['collectionId', 'autoNumberingSchemeId'], + # idFields=[ + # IdField(name='collectionId', column='CollectionID', type='java.lang.Integer'), + # IdField(name='autoNumberingSchemeId', column='AutoNumberingSchemeID', type='java.lang.Integer'), + # ], + # fields=[ + + # ], + # indexes=[ + # Index(name='FK46F04F2A8C2288BA', column_names=['CollectionID']), + # Index(name='FK46F04F2AFE55DD76', column_names=['AutoNumberingSchemeID']) + # ], + # relationships=[ + # Relationship(name='collection', type='many-to-one', required=True, relatedModelName='Collection', column='CollectionID'), + # Relationship(name='autoNumberingScheme', type='many-to-one', required=True, relatedModelName='AutoNumberingScheme', column='AutoNumberingSchemeID') + # ], + # fieldAliases=[ + + # ], + # view=None, + # searchDialog=None, + # skip=True + # ), + # Table( + # classname='edu.ku.brc.specify.datamodel.AutoNumSchDiv', + # table='autonumsch_div', + # tableId=1031, + # idColumns=['DivisionID', 'AutoNumberingSchemeID'], + # idFieldNames=['divisionId', 'autoNumberingSchemeId'], + # idFields=[ + # IdField(name='divisionId', column='DivisionID', type='java.lang.Integer'), + # IdField(name='autoNumberingSchemeId', column='AutoNumberingSchemeID', type='java.lang.Integer'), + # ], + # fields=[ + + # ], + # indexes=[ + # Index(name='FKA8BE49397C961D8', column_names=['DivisionID']), + # Index(name='FKA8BE493FE55DD76', column_names=['AutoNumberingSchemeID']) + # ], + # relationships=[ + # Relationship(name='division', type='many-to-one', required=True, relatedModelName='Division', column='DivisionID'), + # Relationship(name='autoNumberingScheme', type='many-to-one', required=True, relatedModelName='AutoNumberingScheme', column='AutoNumberingSchemeID') + # ], + # fieldAliases=[ + + # ], + # view=None, + # searchDialog=None, + # skip=True + # ), + # Table( + # classname='edu.ku.brc.specify.datamodel.AutoNumSchDsp', + # table='autonumsch_dsp', + # tableId=1032, + # idColumns=['DisciplineID', 'AutoNumberingSchemeID'], + # idFieldNames=['disciplineId', 'autoNumberingSchemeId'], + # idFields=[ + # IdField(name='disciplineId', column='DisciplineID', type='java.lang.Integer'), + # IdField(name='autoNumberingSchemeId', column='AutoNumberingSchemeID', type='java.lang.Integer'), + # ], + # fields=[ + + # ], + # indexes=[ + # Index(name='FKA8BE5C34CE675DE', column_names=['DisciplineID']), + # Index(name='FKA8BE5C3FE55DD76', column_names=['AutoNumberingSchemeID']) + # ], + # relationships=[ + # Relationship(name='discipline', type='many-to-one', required=True, relatedModelName='Discipline', column='DisciplineID'), + # Relationship(name='autoNumberingScheme', type='many-to-one', required=True, relatedModelName='AutoNumberingScheme', column='AutoNumberingSchemeID') + # ], + # fieldAliases=[ + + # ], + # view=None, + # searchDialog=None, + # skip=True + # ), + Table( + classname='edu.ku.brc.specify.datamodel.DeaccessionPreparation', + table='deaccessionpreparation', + tableId=1033, + idColumn='DeaccessionPreparationID', + idFieldName='deaccessionPreparationId', + idField=IdField(name='deaccessionPreparationId', column='DeaccessionPreparationID', type='java.lang.Integer'), + fields=[ + Field(name='quantity', column='Quantity', indexed=False, unique=False, required=False, type='java.lang.Integer'), + Field(name='remarks', column='Remarks', indexed=False, unique=False, required=False, type='text', length=4096), + Field(name='timestampCreated', column='TimestampCreated', indexed=False, unique=False, required=True, type='java.sql.Timestamp'), + Field(name='timestampModified', column='TimestampModified', indexed=False, unique=False, required=False, type='java.sql.Timestamp'), + Field(name='version', column='version', indexed=False, unique=False, required=False, type='java.lang.Integer') + ], + indexes=[ + Index(name='FK6A06F1F47699B003', column_names=['CreatedByAgentID']), + Index(name='FK6A06F1F45327F942', column_names=['ModifiedByAgentID']), + Index(name='FK6A06F1F4BE26B05E', column_names=['DeaccessionID']), + Index(name='FK6A06F1F418627F06', column_names=['PreparationID']) + ], + relationships=[ + Relationship(name='deaccession', type='many-to-one', required=True, relatedModelName='Deaccession', column='DeaccessionID'), + Relationship(name='createdByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='CreatedByAgentID'), + Relationship(name='modifiedByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='ModifiedByAgentID'), + Relationship(name='preparation', type='many-to-one', required=False, relatedModelName='Preparation', column='PreparationID') + ], + fieldAliases=[ + + ], + view=None, + searchDialog=None + ), + # Table( + # classname='edu.ku.brc.specify.datamodel.ProjectColobj', + # table='project_colobj', + # tableId=1034, + # idColumns=['ProjectID', 'CollectionObjectID'], + # idFieldNames=['projectId', 'collectionObjectId'], + # idFields=[ + # IdField(name='projectId', column='ProjectID', type='java.lang.Integer'), + # IdField(name='collectionObjectId', column='CollectionObjectID', type='java.lang.Integer'), + # ], + # fields=[ + + # ], + # indexes=[ + # Index(name='FK1E416F5DAF28760A', column_names=['ProjectID']), + # Index(name='FK1E416F5D75E37458', column_names=['CollectionObjectID']) + # ], + # relationships=[ + # Relationship(name='project', type='many-to-one', required=True, relatedModelName='Project', column='ProjectID'), + # Relationship(name='collectionObject', type='many-to-one', required=True, relatedModelName='CollectionObject', column='CollectionObjectID') + # ], + # fieldAliases=[ + + # ], + # view=None, + # searchDialog=None, + # skip=True + # ), + Table( + classname='edu.ku.brc.specify.datamodel.SgrBatchMatchResultItem', + table='sgrbatchmatchresultitem', + tableId=1035, + idColumn='id', + idFieldName='id', + idField=IdField(name='id', column='id', type='java.lang.Long'), + fields=[ + Field(name='matchedId', column='matchedId', indexed=False, unique=False, required=True, type='java.lang.String', length=128), + Field(name='maxScore', column='maxScore', indexed=False, unique=False, required=True, type='java.lang.Double'), + Field(name='qTime', column='qTime', indexed=False, unique=False, required=True, type='java.lang.Integer') + ], + indexes=[ + Index(name='sgrbatchmatchresultitemfk1', column_names=['batchMatchResultSetId']) + ], + relationships=[ + Relationship(name='batchMatchResultSet', type='many-to-one', required=True, relatedModelName='SgrBatchMatchResultSet', column='batchMatchResultSetId') + ], + fieldAliases=[ + + ], + view=None, + searchDialog=None + ), + Table( + classname='edu.ku.brc.specify.datamodel.SgrBatchMatchResultSet', + table='sgrbatchmatchresultset', + tableId=1039, + idColumn='id', + idFieldName='id', + idField=IdField(name='id', column='id', type='java.lang.Long'), + fields=[ + Field(name='insertTime', column='insertTime', indexed=False, unique=False, required=True, type='java.sql.Timestamp'), + Field(name='name', column='name', indexed=False, unique=False, required=True, type='java.lang.String', length=128), + Field(name='recordSetID', column='recordSetID', indexed=False, unique=False, required=False, type='java.lang.Long'), + Field(name='query', column='query', indexed=False, unique=False, required=True, type='text', length=65535), + Field(name='remarks', column='remarks', indexed=False, unique=False, required=True, type='text', length=65535), + Field(name='dbTableId', column='dbTableId', indexed=False, unique=False, required=False, type='java.lang.Integer'), + ], + indexes=[ + Index(name='sgrbatchmatchresultsetfk2', column_names=['matchConfigurationId']), + ], + relationships=[ + Relationship( + name='items', + type='one-to-many', + required=False, + relatedModelName='SgrBatchMatchResultItem', + otherSideName='batchMatchResultSet', + ), + Relationship( + name='matchConfiguration', + type='many-to-one', + required=True, + relatedModelName='SgrMatchConfiguration', + column='matchConfigurationId', + otherSideName='batchMatchResultSets', + ), + ], + fieldAliases=[], + view=None, + searchDialog=None, + ), + Table( + classname='edu.ku.brc.specify.datamodel.SgrMatchConfiguration', + table='sgrmatchconfiguration', + tableId=1040, + idColumn='id', + idFieldName='id', + idField=IdField(name='id', column='id', type='java.lang.Long'), + fields=[ + Field(name='name', column='name', indexed=False, unique=False, required=True, type='java.lang.String', length=128), + Field(name='similarityFields', column='similarityFields', indexed=False, unique=False, required=True, type='text', length=65535), + Field(name='serverUrl', column='serverUrl', indexed=False, unique=False, required=True, type='text', length=65535), + Field(name='filterQuery', column='filterQuery', indexed=False, unique=False, required=True, type='java.lang.String', length=128), + Field(name='queryFields', column='queryFields', indexed=False, unique=False, required=True, type='text', length=65535), + Field(name='remarks', column='remarks', indexed=False, unique=False, required=True, type='text', length=65535), + Field(name='boostInterestingTerms', column='boostInterestingTerms', indexed=False, unique=False, required=True, type='java.lang.Boolean'), + Field(name='nRows', column='nRows', indexed=False, unique=False, required=True, type='java.lang.Integer'), + ], + indexes=[], + relationships=[ + Relationship(name='batchMatchResultSets', type='one-to-many', required=False, relatedModelName='SgrBatchMatchResultSet', otherSideName='matchConfiguration'), + ], + fieldAliases=[], + view=None, + searchDialog=None + ), + # Table( + # classname='edu.ku.brc.specify.datamodel.SpSchemaMapping', + # table='sp_schema_mapping', + # tableId=1036, + # idColumns=['SpExportSchemaID', 'SpExportSchemaMappingID'], + # idFieldNames=['spExportSchemaId', 'spExportSchemaMappingId'], + # idFields=[ + # IdField(name='spExportSchemaId', column='SpExportSchemaID', type='java.lang.Integer'), + # IdField(name='spExportSchemaMappingId', column='SpExportSchemaMappingID', type='java.lang.Integer'), + # ], + # fields=[ + + # ], + # indexes=[ + # Index(name='FKC5EDFE525722A7A2', column_names=['SpExportSchemaID']), + # Index(name='FKC5EDFE52F7C8AAB0', column_names=['SpExportSchemaMappingID']) + # ], + # relationships=[ + # Relationship(name='spExportSchema', type='many-to-one', required=True, relatedModelName='SpExportSchema', column='SpExportSchemaID'), + # Relationship(name='spExportSchemaMapping', type='many-to-one', required=True, relatedModelName='SpExportSchemaMapping', column='SpExportSchemaMappingID') + # ], + # fieldAliases=[ + + # ], + # view=None, + # searchDialog=None, + # skip=True + # ), + # Table( + # classname='edu.ku.brc.specify.datamodel.SpecifyUserSpPrincipal', + # table='specifyuser_spprincipal', + # tableId=1037, + # idColumns=['SpecifyUserID', 'SpPrincipalID'], + # idFieldNames=['specifyUserId', 'spPrincipalId'], + # idFields=[ + # IdField(name='specifyUserId', column='SpecifyUserID', type='java.lang.Integer'), + # IdField(name='spPrincipalId', column='SpPrincipalID', type='java.lang.Integer'), + # ], + # fields=[ + + # ], + # indexes=[ + # Index(name='FK81E18B5E4BDD9E10', column_names=['SpecifyUserID']), + # Index(name='FK81E18B5E99A7381A', column_names=['SpPrincipalID']) + # ], + # relationships=[ + # Relationship(name='specifyUser', type='many-to-one', required=True, relatedModelName='SpecifyUser', column='SpecifyUserID'), + # Relationship(name='spPrincipal', type='many-to-one', required=True, relatedModelName='SpPrincipal', column='SpPrincipalID') + # ], + # fieldAliases=[ + + # ], + # view=None, + # searchDialog=None, + # skip=True + # ), + # Table( + # classname='edu.ku.brc.specify.datamodel.SpPrincipalSpPermission', + # table='spprincipal_sppermission', + # tableId=1038, + # idColumns=['SpPrincipalID', 'SpPermissionID'], + # idFieldNames=['spPrincispalId', 'spPermissionId'], + # idFields=[ + # IdField(name='spPrincipalId', column='SpPrincipalID', type='java.lang.Integer'), + # IdField(name='spPermissionId', column='SpPermissionID', type='java.lang.Integer'), + # ], + # fields=[ + + # ], + # indexes=[ + # Index(name='FK9DD8B2FA99A7381A', column_names=['SpPrincipalID']), + # Index(name='FK9DD8B2FA891F8736', column_names=['SpPermissionID']) + # ], + # relationships=[ + # Relationship(name='spPrincipal', type='many-to-one', required=True, relatedModelName='SpPrincipal', column='SpPrincipalID'), + # Relationship(name='spPermission', type='many-to-one', required=True, relatedModelName='SpPermission', column='SpPermissionID') + # ], + # fieldAliases=[ + + # ], + # view=None, + # searchDialog=None, + # skip=True + # ), ]) # add_collectingevents_to_locality(datamodel) # added statically to datamodel definitions diff --git a/specifyweb/specify/migrations/0001_initial.py b/specifyweb/specify/migrations/0001_initial.py index 5c037acb8e4..8a281d3f5a4 100644 --- a/specifyweb/specify/migrations/0001_initial.py +++ b/specifyweb/specify/migrations/0001_initial.py @@ -2430,7 +2430,7 @@ class Migration(migrations.Migration): ('version', models.IntegerField(blank=True, db_column='Version', default=0, null=True)), ('createdbyagent', models.ForeignKey(db_column='CreatedByAgentID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.agent')), ('modifiedbyagent', models.ForeignKey(db_column='ModifiedByAgentID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.agent')), - ('parent', models.ForeignKey(db_column='ParentItemID', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='specify.taxontreedefitem')), + ('parent', models.ForeignKey(db_column='ParentItemID', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='specify.taxontreedefitem')), ('treedef', models.ForeignKey(db_column='TaxonTreeDefID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='treedefitems', to='specify.taxontreedef')), ], options={ @@ -6977,4 +6977,221 @@ class Migration(migrations.Migration): model_name='accession', index=models.Index(fields=['dateaccessioned'], name='AccessionDateIDX'), ), + migrations.CreateModel( + name='SpecifyuserSpprincipal', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'specifyuser_spprincipal', + 'ordering': (), + 'managed': False, + }, + ), + migrations.CreateModel( + name='Sgrmatchconfiguration', + fields=[ + ('id', models.BigAutoField(db_column='id', primary_key=True, serialize=False)), + ('name', models.CharField(db_column='name', max_length=128)), + ('similarityfields', models.TextField(db_column='similarityFields')), + ('serverurl', models.TextField(db_column='serverUrl')), + ('filterquery', models.CharField(db_column='filterQuery', max_length=128)), + ('queryfields', models.TextField(db_column='queryFields')), + ('remarks', models.TextField(db_column='remarks')), + ('boostinterestingterms', models.BooleanField(db_column='boostInterestingTerms')), + ('nrows', models.IntegerField(db_column='nRows')), + ], + options={ + 'db_table': 'sgrmatchconfiguration', + 'ordering': (), + }, + ), + migrations.AlterField( + model_name='taxontreedefitem', + name='parent', + field=models.ForeignKey(db_column='ParentItemID', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='specify.taxontreedefitem'), + ), + migrations.CreateModel( + name='SpprincipalSppermission', + fields=[ + ('sppermission', models.ForeignKey(db_column='SpPermissionID', on_delete=specifyweb.specify.models.protect_with_blockers, primary_key=True, related_name='spprincipal_links', serialize=False, to='specify.sppermission')), + ('spprincipal', models.ForeignKey(db_column='SpPrincipalID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='sppermission_links', to='specify.spprincipal')), + ], + options={ + 'db_table': 'spprincipal_sppermission', + 'ordering': (), + 'unique_together': {('sppermission', 'spprincipal')}, + }, + ), + migrations.CreateModel( + name='Sgrbatchmatchresultset', + fields=[ + ('id', models.BigAutoField(db_column='id', primary_key=True, serialize=False)), + ('inserttime', models.DateTimeField(db_column='insertTime', default=django.utils.timezone.now)), + ('name', models.CharField(db_column='name', max_length=128)), + ('recordsetid', models.BigIntegerField(blank=True, db_column='recordSetID', null=True)), + ('query', models.TextField(db_column='query')), + ('remarks', models.TextField(db_column='remarks')), + ('dbtableid', models.IntegerField(blank=True, db_column='dbTableId', null=True)), + ('matchconfiguration', models.ForeignKey(db_column='matchConfigurationId', on_delete=django.db.models.deletion.DO_NOTHING, related_name='batchmatchresultsets', to='specify.sgrmatchconfiguration')), + ], + options={ + 'db_table': 'sgrbatchmatchresultset', + 'ordering': (), + }, + ), + migrations.CreateModel( + name='Sgrbatchmatchresultitem', + fields=[ + ('id', models.BigAutoField(db_column='id', primary_key=True, serialize=False)), + ('matchedid', models.CharField(db_column='matchedId', max_length=128)), + ('maxscore', models.FloatField(db_column='maxScore')), + ('qtime', models.IntegerField(db_column='qTime')), + ('batchmatchresultset', models.ForeignKey(db_column='batchMatchResultSetId', on_delete=django.db.models.deletion.CASCADE, related_name='items', to='specify.sgrbatchmatchresultset')), + ], + options={ + 'db_table': 'sgrbatchmatchresultitem', + 'ordering': (), + }, + ), + migrations.CreateModel( + name='ProjectColobj', + fields=[ + ('project', models.ForeignKey(db_column='ProjectID', on_delete=specifyweb.specify.models.protect_with_blockers, primary_key=True, related_name='+', serialize=False, to='specify.project')), + ('collectionobject', models.ForeignKey(db_column='CollectionObjectID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.collectionobject')), + ], + options={ + 'db_table': 'project_colobj', + 'ordering': (), + }, + ), + migrations.CreateModel( + name='Deaccessionpreparation', + fields=[ + ('id', models.AutoField(db_column='DeaccessionPreparationID', primary_key=True, serialize=False)), + ('quantity', models.SmallIntegerField(blank=True, db_column='Quantity', null=True)), + ('remarks', models.TextField(blank=True, db_column='Remarks', null=True)), + ('timestampcreated', models.DateTimeField(db_column='TimestampCreated', default=django.utils.timezone.now)), + ('timestampmodified', models.DateTimeField(blank=True, db_column='TimestampModified', default=django.utils.timezone.now, null=True)), + ('version', models.IntegerField(blank=True, db_column='version', default=0, null=True)), + ('createdbyagent', models.ForeignKey(db_column='CreatedByAgentID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.agent')), + ('deaccession', models.ForeignKey(db_column='DeaccessionID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.deaccession')), + ('modifiedbyagent', models.ForeignKey(db_column='ModifiedByAgentID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.agent')), + ('preparation', models.ForeignKey(db_column='PreparationID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.preparation')), + ], + options={ + 'db_table': 'deaccessionpreparation', + 'ordering': (), + }, + ), + migrations.CreateModel( + name='AutonumschDsp', + fields=[ + ('discipline', models.ForeignKey(db_column='DisciplineID', on_delete=specifyweb.specify.models.protect_with_blockers, primary_key=True, related_name='+', serialize=False, to='specify.discipline')), + ('autonumberingscheme', models.ForeignKey(db_column='AutoNumberingSchemeID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.autonumberingscheme')), + ], + options={ + 'db_table': 'autonumsch_dsp', + 'ordering': (), + }, + ), + migrations.CreateModel( + name='AutonumschDiv', + fields=[ + ('division', models.ForeignKey(db_column='DivisionID', on_delete=specifyweb.specify.models.protect_with_blockers, primary_key=True, related_name='+', serialize=False, to='specify.division')), + ('autonumberingscheme', models.ForeignKey(db_column='AutoNumberingSchemeID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.autonumberingscheme')), + ], + options={ + 'db_table': 'autonumsch_div', + 'ordering': (), + }, + ), + migrations.CreateModel( + name='AutonumschColl', + fields=[ + ('collection', models.ForeignKey(db_column='CollectionID', on_delete=specifyweb.specify.models.protect_with_blockers, primary_key=True, related_name='+', serialize=False, to='specify.collection')), + ('autonumberingscheme', models.ForeignKey(db_column='AutoNumberingSchemeID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.autonumberingscheme')), + ], + options={ + 'db_table': 'autonumsch_coll', + 'ordering': (), + }, + ), + migrations.AddField( + model_name='autonumberingscheme', + name='collections', + field=models.ManyToManyField(related_name='autonumberingschemes', through='specify.AutonumschColl', to='specify.collection'), + ), + migrations.AddField( + model_name='autonumberingscheme', + name='disciplines', + field=models.ManyToManyField(related_name='autonumberingschemes', through='specify.AutonumschDsp', to='specify.discipline'), + ), + migrations.AddField( + model_name='autonumberingscheme', + name='divisions', + field=models.ManyToManyField(related_name='autonumberingschemes', through='specify.AutonumschDiv', to='specify.division'), + ), + migrations.AddField( + model_name='project', + name='collectionobjects', + field=models.ManyToManyField(related_name='projects', through='specify.ProjectColobj', to='specify.collectionobject'), + ), + migrations.AddField( + model_name='specifyuser', + name='spprincipals', + field=models.ManyToManyField(related_name='spprincipals', through='specify.SpecifyuserSpprincipal', to='specify.spprincipal'), + ), + migrations.AddField( + model_name='spprincipal', + name='sppermissions', + field=models.ManyToManyField(related_name='spprincipals', through='specify.SpprincipalSppermission', to='specify.sppermission'), + ), + migrations.CreateModel( + name='SpSchemaMapping', + fields=[ + ('spexportschemamapping', models.ForeignKey(db_column='SpExportSchemaMappingID', on_delete=specifyweb.specify.models.protect_with_blockers, primary_key=True, related_name='sp_schema_mappings', serialize=False, to='specify.spexportschemamapping')), + ('spexportschema', models.ForeignKey(db_column='SpExportSchemaID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='sp_schema_mappings', to='specify.spexportschema')), + ], + options={ + 'db_table': 'sp_schema_mapping', + 'ordering': (), + 'unique_together': {('spexportschemamapping', 'spexportschema')}, + }, + ), + migrations.AddIndex( + model_name='sgrbatchmatchresultset', + index=models.Index(fields=['matchconfiguration'], name='sgrbatchmatchresultsetfk2'), + ), + migrations.AlterUniqueTogether( + name='projectcolobj', + unique_together={('project', 'collectionobject')}, + ), + migrations.AlterUniqueTogether( + name='autonumschdsp', + unique_together={('discipline', 'autonumberingscheme')}, + ), + migrations.AlterUniqueTogether( + name='autonumschdiv', + unique_together={('division', 'autonumberingscheme')}, + ), + migrations.AlterUniqueTogether( + name='autonumschcoll', + unique_together={('collection', 'autonumberingscheme')}, + ), + migrations.AlterField( + model_name='deaccessionpreparation', + name='deaccession', + field=models.ForeignKey(db_column='DeaccessionID', on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.deaccession'), + ), + migrations.AlterField( + model_name='deaccessionpreparation', + name='preparation', + field=models.ForeignKey(db_column='PreparationID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='+', to='specify.preparation'), + ), + migrations.AlterField( + model_name='taxontreedefitem', + name='parent', + field=models.ForeignKey(db_column='ParentItemID', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='specify.taxontreedefitem'), + ), ] diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index d78f01d3e0c..91a702a2663 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -328,6 +328,7 @@ class Agent(models.Model): modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) organization = models.ForeignKey('Agent', db_column='ParentOrganizationID', related_name='orgmembers', null=True, on_delete=protect_with_blockers) specifyuser = models.ForeignKey('SpecifyUser', db_column='SpecifyUserID', related_name='agents', null=True, on_delete=models.SET_NULL) + # institutiontc = models.ForeignKey("InstitutionNetwork", db_column="InstitutionTCID", on_delete=models.DO_NOTHING, null=True, blank=True, related_name="agents_institutiontc") class Meta: db_table = 'agent' @@ -741,6 +742,26 @@ class Autonumberingscheme(models.Model): createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) + # Relationships: Many-to-Many + collections = models.ManyToManyField( + "Collection", + through="specify.Autonumschcoll", + through_fields=("autonumberingscheme", "collection"), + related_name="autonumberingschemes" + ) + disciplines = models.ManyToManyField( + "Discipline", + through="specify.AutonumschDsp", + through_fields=("autonumberingscheme", "discipline"), + related_name="autonumberingschemes" + ) + divisions = models.ManyToManyField( + "Division", + through="specify.AutonumschDiv", + through_fields=("autonumberingscheme", "division"), + related_name="autonumberingschemes" + ) + class Meta: db_table = 'autonumberingscheme' ordering = () @@ -1380,6 +1401,7 @@ class Collection(models.Model): institutionnetwork = models.ForeignKey('Institution', db_column='InstitutionNetworkID', related_name='+', null=True, on_delete=protect_with_blockers) modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) collectionobjecttype = models.ForeignKey('CollectionObjectType', db_column='CollectionObjectTypeID', related_name='collections', null=True, on_delete=models.SET_NULL) + # institutionnetwork = models.ForeignKey("InstitutionNetwork", db_column="InstitutionNetworkID", on_delete=models.DO_NOTHING, null=True, blank=True, related_name="collections") class Meta: db_table = 'collection' @@ -2882,6 +2904,7 @@ class Discipline(model_extras.Discipline): timestampmodified = models.DateTimeField(blank=True, null=True, unique=False, db_column='TimestampModified', db_index=False, default=timezone.now) # auto_now=True type = models.CharField(blank=True, max_length=64, null=True, unique=False, db_column='Type', db_index=False) version = models.IntegerField(blank=True, null=False, unique=False, db_column='Version', db_index=False, default=0) + # disciplineid = models.IntegerField(blank=True, null=False, unique=False, db_column='DisciplineId', db_index=False) # Relationships: Many-to-One createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) @@ -5583,6 +5606,14 @@ class Project(models.Model): createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) + # Relationships: Many-to-Many + collectionobjects = models.ManyToManyField( + 'CollectionObject', + through="specify.ProjectColobj", + through_fields=("project", "collectionobject"), + related_name="projects" + ) + class Meta: db_table = 'project' ordering = () @@ -6309,6 +6340,9 @@ class Spprincipal(models.Model): modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) userGroupScopeID = models.IntegerField(blank=True, null=True, db_column='userGroupScopeID') + # Relationships: Many-to-Many + sppermissions = models.ManyToManyField("SpPermission", through="specify.SpprincipalSppermission", through_fields=("spprincipal", "sppermission"), related_name="spprincipals") + class Meta: db_table = 'spprincipal' ordering = () @@ -6609,6 +6643,9 @@ class Specifyuser(model_extras.Specifyuser): createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) + # Relationships: Many-to-Many + spprincipals = models.ManyToManyField("SpPrincipal", through="specify.SpecifyuserSpprincipal", through_fields=("specifyuser", "spprincipal"), related_name="spprincipals") + class Meta: db_table = 'specifyuser' ordering = () @@ -7988,4 +8025,195 @@ class Meta: db_table = 'tectonicunit' ordering = () - save = partialmethod(custom_save) \ No newline at end of file + save = partialmethod(custom_save) + + + +class AutonumschColl(models.Model): + # specify_model = datamodel.get_table_strict('autonumsch_coll') + + collection = models.ForeignKey('Collection', db_column='CollectionID', related_name='+', null=False, on_delete=protect_with_blockers, primary_key=True) + autonumberingscheme = models.ForeignKey('AutoNumberingScheme', db_column='AutoNumberingSchemeID', related_name='+', null=False, on_delete=protect_with_blockers) + + class Meta: + db_table = 'autonumsch_coll' + ordering = () + unique_together = (('collection', 'autonumberingscheme'),) + + save = partialmethod(custom_save) + +class AutonumschDiv(models.Model): + # specify_model = datamodel.get_table_strict('autonumsch_div') + + division = models.ForeignKey('Division', db_column='DivisionID', related_name='+', null=False, on_delete=protect_with_blockers, primary_key=True) + autonumberingscheme = models.ForeignKey('AutoNumberingScheme', db_column='AutoNumberingSchemeID', related_name='+', null=False, on_delete=protect_with_blockers) + + class Meta: + db_table = 'autonumsch_div' + ordering = () + unique_together = (('division', 'autonumberingscheme'),) + + save = partialmethod(custom_save) + +class AutonumschDsp(models.Model): + # specify_model = datamodel.get_table_strict('autonumsch_dsp') + + discipline = models.ForeignKey('Discipline', db_column='DisciplineID', related_name='+', null=False, on_delete=protect_with_blockers, primary_key=True) + autonumberingscheme = models.ForeignKey('AutoNumberingScheme', db_column='AutoNumberingSchemeID', related_name='+', null=False, on_delete=protect_with_blockers) + + class Meta: + db_table = 'autonumsch_dsp' + ordering = () + unique_together = (('discipline', 'autonumberingscheme'),) + + save = partialmethod(custom_save) + +class SpecifyuserSpprincipal(models.Model): + # specify_model = datamodel.get_table_strict('specifyuser_spprincipal') + + specifyuser = models.ForeignKey('SpecifyUser', db_column='SpecifyUserID', on_delete=models.CASCADE, related_name="+") + spprincipal = models.ForeignKey('SpPrincipal', db_column='SpPrincipalID', on_delete=models.deletion.DO_NOTHING, related_name="+") + + class Meta: + db_table = 'specifyuser_spprincipal' + managed = False + ordering = () + unique_together = (('specifyuser', 'spprincipal'),) + indexes = [ + models.Index(fields=['specifyuser'], name='FK81E18B5E4BDD9E10'), + models.Index(fields=['spprincipal'], name='FK81E18B5E99A7381A'), + ] + +class Deaccessionpreparation(models.Model): + specify_model = datamodel.get_table_strict('deaccessionpreparation') + + # ID Field + id = models.AutoField(primary_key=True, db_column='DeaccessionPreparationID') + + # Fields + quantity = models.SmallIntegerField(blank=True, null=True, unique=False, db_column='Quantity', db_index=False) + remarks = models.TextField(blank=True, null=True, unique=False, db_column='Remarks', db_index=False) + timestampcreated = models.DateTimeField(blank=False, null=False, unique=False, db_column='TimestampCreated', db_index=False, default=timezone.now) + timestampmodified = models.DateTimeField(blank=True, null=True, unique=False, db_column='TimestampModified', db_index=False, default=timezone.now) + version = models.IntegerField(blank=True, null=True, unique=False, db_column='version', db_index=False, default=0) + + # Relationships: Many-to-One + deaccession = models.ForeignKey('Deaccession', db_column='DeaccessionID', related_name='+', null=False, on_delete=protect_with_blockers) + createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) + modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) + preparation = models.ForeignKey('Preparation', db_column='PreparationID', related_name='+', null=True, on_delete=protect_with_blockers) + + class Meta: + db_table = 'deaccessionpreparation' + ordering = () + + save = partialmethod(custom_save) + +class ProjectColobj(models.Model): + # specify_model = datamodel.get_table_strict('project_colobj') + + # Composite PK table (no AutoField); use the two FKs as the PK + project = models.ForeignKey('Project', db_column='ProjectID', related_name='+', null=False, on_delete=protect_with_blockers, primary_key=True) + collectionobject = models.ForeignKey('CollectionObject', db_column='CollectionObjectID', related_name='+', null=False, on_delete=protect_with_blockers) + + class Meta: + db_table = 'project_colobj' + ordering = () + unique_together = (('project', 'collectionobject'),) + + save = partialmethod(custom_save) + +class Sgrbatchmatchresultitem(models.Model): + specify_model = datamodel.get_table_strict('sgrbatchmatchresultitem') + + # ID Field + id = models.BigAutoField(primary_key=True, db_column='id') + + # Fields + matchedid = models.CharField(blank=False, max_length=128, null=False, unique=False, db_column='matchedId', db_index=False) + maxscore = models.FloatField(blank=False, null=False, unique=False, db_column='maxScore', db_index=False) + qtime = models.IntegerField(blank=False, null=False, unique=False, db_column='qTime', db_index=False) + + # Relationships: Many-to-One + batchmatchresultset = models.ForeignKey('SgrBatchMatchResultSet', db_column='batchMatchResultSetId', related_name='items', null=False, on_delete=models.CASCADE) + + class Meta: + db_table = 'sgrbatchmatchresultitem' + ordering = () + + save = partialmethod(custom_save) + +class Sgrbatchmatchresultset(models.Model): + specify_model = datamodel.get_table_strict('sgrbatchmatchresultset') + + # ID Field + id = models.BigAutoField(primary_key=True, db_column='id') + + # Fields + inserttime = models.DateTimeField( blank=False, null=False, unique=False, db_column='insertTime', db_index=False, default=timezone.now) + name = models.CharField( blank=False, max_length=128, null=False, unique=False, db_column='name', db_index=False) + recordsetid = models.BigIntegerField( blank=True, null=True, unique=False, db_column='recordSetID', db_index=False) + query = models.TextField( blank=False, null=False, unique=False, db_column='query', db_index=False) + remarks = models.TextField( blank=False, null=False, unique=False, db_column='remarks', db_index=False) + dbtableid = models.IntegerField( blank=True, null=True, unique=False, db_column='dbTableId', db_index=False) + + # Relationships + matchconfiguration = models.ForeignKey('Sgrmatchconfiguration', db_column='matchConfigurationId', related_name='batchmatchresultsets', null=False, on_delete=models.DO_NOTHING) + + class Meta: + db_table = 'sgrbatchmatchresultset' + ordering = () + indexes = [ + models.Index(fields=['matchconfiguration'], name='sgrbatchmatchresultsetfk2'), + ] + + save = partialmethod(custom_save) + +class Sgrmatchconfiguration(models.Model): + specify_model = datamodel.get_table_strict('sgrmatchconfiguration') + + # ID Field + id = models.BigAutoField(primary_key=True, db_column='id') + + # Fields + name = models.CharField(blank=False, max_length=128, null=False, unique=False, db_column='name', db_index=False) + similarityfields = models.TextField(blank=False, null=False, unique=False, db_column='similarityFields', db_index=False) + serverurl = models.TextField(blank=False, null=False, unique=False, db_column='serverUrl', db_index=False) + filterquery = models.CharField(blank=False, max_length=128, null=False, unique=False, db_column='filterQuery', db_index=False) + queryfields = models.TextField(blank=False, null=False, unique=False, db_column='queryFields', db_index=False) + remarks = models.TextField(blank=False, null=False, unique=False, db_column='remarks', db_index=False) + boostinterestingterms = models.BooleanField(blank=False, null=False, unique=False, db_column='boostInterestingTerms', db_index=False) + nrows = models.IntegerField(blank=False, null=False, unique=False, db_column='nRows', db_index=False) + + class Meta: + db_table = 'sgrmatchconfiguration' + ordering = () + + save = partialmethod(custom_save) + +class SpSchemaMapping(models.Model): + # specify_model = datamodel.get_table_strict('sp_schema_mapping') + + # Composite PK table; use one FK as primary key + unique_together + spexportschemamapping = models.ForeignKey('SpExportSchemaMapping', db_column='SpExportSchemaMappingID', related_name='sp_schema_mappings', null=False, on_delete=protect_with_blockers, primary_key=True) + spexportschema = models.ForeignKey('SpExportSchema', db_column='SpExportSchemaID', related_name='sp_schema_mappings', null=False, on_delete=protect_with_blockers) + + class Meta: + db_table = 'sp_schema_mapping' + ordering = () + unique_together = (('spexportschemamapping', 'spexportschema'),) + + save = partialmethod(custom_save) + +class SpprincipalSppermission(models.Model): + # specify_model = datamodel.get_table_strict('spprincipal_sppermission') + + sppermission = models.ForeignKey('SpPermission', db_column='SpPermissionID', related_name='spprincipal_links', null=False, on_delete=protect_with_blockers, primary_key=True) + spprincipal = models.ForeignKey('SpPrincipal', db_column='SpPrincipalID', related_name='sppermission_links', null=False, on_delete=protect_with_blockers) + + class Meta: + db_table = 'spprincipal_sppermission' + ordering = () + unique_together = (('sppermission', 'spprincipal'),) + + save = partialmethod(custom_save) diff --git a/specifyweb/specify/models_utils/load_datamodel.py b/specifyweb/specify/models_utils/load_datamodel.py index 0432496fab0..0077c7be8e5 100644 --- a/specifyweb/specify/models_utils/load_datamodel.py +++ b/specifyweb/specify/models_utils/load_datamodel.py @@ -650,4 +650,4 @@ def flag_system_tables(datamodel: Datamodel) -> None: 'Workbenchrowimage', 'Workbenchtemplate', 'Workbenchtemplatemappingitem', -} +} \ No newline at end of file diff --git a/specifyweb/specify/models_utils/model_timestamp.py b/specifyweb/specify/models_utils/model_timestamp.py index 0080174df18..9ff1a3c6811 100644 --- a/specifyweb/specify/models_utils/model_timestamp.py +++ b/specifyweb/specify/models_utils/model_timestamp.py @@ -12,9 +12,11 @@ def save_auto_timestamp_field_with_override(save_func, args, kwargs, obj): fields_to_update = kwargs.get('update_fields', None) if fields_to_update is None: fields_to_update = [ - field.name for field in model._meta.get_fields(include_hidden=True) if field.concrete - and not field.primary_key - ] + f.name for f in model._meta.get_fields(include_hidden=True) + if getattr(f, "concrete", False) + and not getattr(f, "many_to_many", False) + and not getattr(f, "primary_key", False) + ] if obj.id is not None: fields_to_update = [ diff --git a/specifyweb/specify/models_utils/models_by_table_id.py b/specifyweb/specify/models_utils/models_by_table_id.py index 6132fb5088f..6b5bbc022dc 100644 --- a/specifyweb/specify/models_utils/models_by_table_id.py +++ b/specifyweb/specify/models_utils/models_by_table_id.py @@ -217,7 +217,18 @@ 1026:'Tectonicunittreedefitem', 1027:'Tectonicunit', 1028:'Spdatasetattachment', - 1029: 'Component' + 1029:'Component', + 1030:'AutonumschColl', + 1031:'AutonumschDiv', + 1032:'AutonumschDsp', + 1033:'Deaccessionpreparation', + 1034:'ProjectColobj', + 1035:'Sgrbatchmatchresultitem', + 1036:'SpSchemaMapping', + 1037:'SpecifyuserSpprincipal', + 1038:'SpprincipalSppermission', + 1039: 'Sgrbatchmatchresultset', + 1040: 'Sgrmatchconfiguration', } model_names_by_app = { @@ -448,7 +459,18 @@ 'Tectonicunittreedef', 'Tectonicunittreedefitem', 'Tectonicunit', - 'Component' + 'Component', + 'AutonumschColl', + 'AutonumschDiv', + 'AutonumschDsp', + 'Deaccessionpreparation', + 'ProjectColobj', + 'Sgrbatchmatchresultitem', + 'SpSchemaMapping', + 'SpecifyuserSpprincipal', + 'SpprincipalSppermission', + 'Sgrbatchmatchresultset', + 'Sgrmatchconfiguration', } }