diff --git a/config/common/common.views.xml b/config/common/common.views.xml index 573e1e68532..c5e1d67c7d9 100644 --- a/config/common/common.views.xml +++ b/config/common/common.views.xml @@ -3514,7 +3514,8 @@ - --> + + --> @@ -4202,7 +4203,22 @@ - --> + + + + + + + + + + + + + + + + --> @@ -4782,7 +4798,21 @@ - --> + + + + + + + + + + + + + + + --> diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts index 3b38f5ce27b..ffe95745858 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts @@ -3369,6 +3369,11 @@ export type Gift = { }; readonly toOneDependent: { readonly addressOfRecord: AddressOfRecord | null }; readonly toOneIndependent: { + readonly agent1: Agent | null; + readonly agent2: Agent | null; + readonly agent3: Agent | null; + readonly agent4: Agent | null; + readonly agent5: Agent | null; readonly createdByAgent: Agent | null; readonly deaccession: Deaccession | null; readonly discipline: Discipline; @@ -3762,6 +3767,11 @@ export type Loan = { }; readonly toOneDependent: { readonly addressOfRecord: AddressOfRecord | null }; readonly toOneIndependent: { + readonly agent1: Agent | null; + readonly agent2: Agent | null; + readonly agent3: Agent | null; + readonly agent4: Agent | null; + readonly agent5: Agent | null; readonly createdByAgent: Agent | null; readonly discipline: Discipline; readonly division: Division | null; diff --git a/specifyweb/specify/datamodel.py b/specifyweb/specify/datamodel.py index 10f1d2f331a..da4f561043f 100644 --- a/specifyweb/specify/datamodel.py +++ b/specifyweb/specify/datamodel.py @@ -4065,6 +4065,11 @@ def is_tree_table(table: Table): ], relationships=[ Relationship(name='addressOfRecord', type='many-to-one',required=False, relatedModelName='AddressOfRecord', column='AddressOfRecordID', dependent=True), + Relationship(name='agent1', type='many-to-one',required=False, relatedModelName='Agent', column='Agent1ID'), + Relationship(name='agent2', type='many-to-one',required=False, relatedModelName='Agent', column='Agent2ID'), + Relationship(name='agent3', type='many-to-one',required=False, relatedModelName='Agent', column='Agent3ID'), + Relationship(name='agent4', type='many-to-one',required=False, relatedModelName='Agent', column='Agent4ID'), + Relationship(name='agent5', type='many-to-one',required=False, relatedModelName='Agent', column='Agent5ID'), Relationship(name='createdByAgent', type='many-to-one',required=False, relatedModelName='Agent', column='CreatedByAgentID'), Relationship(name='deaccession', type='many-to-one',required=False, relatedModelName='Deaccession', column='DeaccessionID', otherSideName='gifts'), Relationship(name='discipline', type='many-to-one',required=True, relatedModelName='Discipline', column='DisciplineID'), @@ -4591,6 +4596,11 @@ def is_tree_table(table: Table): ], relationships=[ Relationship(name='addressOfRecord', type='many-to-one',required=False, relatedModelName='AddressOfRecord', column='AddressOfRecordID', otherSideName='loans', dependent=True), + Relationship(name='agent1', type='many-to-one',required=False, relatedModelName='Agent', column='Agent1ID'), + Relationship(name='agent2', type='many-to-one',required=False, relatedModelName='Agent', column='Agent2ID'), + Relationship(name='agent3', type='many-to-one',required=False, relatedModelName='Agent', column='Agent3ID'), + Relationship(name='agent4', type='many-to-one',required=False, relatedModelName='Agent', column='Agent4ID'), + Relationship(name='agent5', type='many-to-one',required=False, relatedModelName='Agent', column='Agent5ID'), Relationship(name='createdByAgent', type='many-to-one',required=False, relatedModelName='Agent', column='CreatedByAgentID'), Relationship(name='discipline', type='many-to-one',required=True, relatedModelName='Discipline', column='DisciplineID'), Relationship(name='division', type='many-to-one',required=False, relatedModelName='Division', column='DivisionID'), diff --git a/specifyweb/specify/migration_utils/sp7_schemaconfig.py b/specifyweb/specify/migration_utils/sp7_schemaconfig.py index 943144b5cff..07caa8dc3cd 100644 --- a/specifyweb/specify/migration_utils/sp7_schemaconfig.py +++ b/specifyweb/specify/migration_utils/sp7_schemaconfig.py @@ -221,4 +221,26 @@ ('date2', 'Date 2', 'Date 2'), ('date2Precision', 'Date 2 Precision', 'Date 2 Precision'), ] +} + +MIGRATION_0038_FIELDS = { + 'Loan': ['agent1', 'agent2', 'agent3', 'agent4', 'agent5'], + 'Gift': ['agent1', 'agent2', 'agent3', 'agent4', 'agent5'], +} + +MIGRATION_0038_UPDATE_FIELDS = { + 'Loan': [ + ('agent1','Agent 1','Agent 1'), + ('agent2','Agent 2','Agent 2'), + ('agent3','Agent 3','Agent 3'), + ('agent4','Agent 4','Agent 4'), + ('agent5','Agent 5','Agent 5'), + ], + 'Gift': [ + ('agent1','Agent 1','Agent 1'), + ('agent2','Agent 2','Agent 2'), + ('agent3','Agent 3','Agent 3'), + ('agent4','Agent 4','Agent 4'), + ('agent5','Agent 5','Agent 5'), + ] } \ No newline at end of file diff --git a/specifyweb/specify/migrations/0039_agent_fields_for_loan_and_gift.py b/specifyweb/specify/migrations/0039_agent_fields_for_loan_and_gift.py new file mode 100644 index 00000000000..fa1b172bce3 --- /dev/null +++ b/specifyweb/specify/migrations/0039_agent_fields_for_loan_and_gift.py @@ -0,0 +1,153 @@ +# Generated by Django 4.2.22 on 2025-07-23 21:18 + +from django.apps import apps as specify_apps +from django.db import migrations, models +from specifyweb.specify.models import protect_with_blockers + +from specifyweb.specify.migration_utils.update_schema_config import ( + revert_table_field_schema_config, + update_table_field_schema_config_with_defaults, +) +from specifyweb.specify.migration_utils.sp7_schemaconfig import ( + MIGRATION_0038_FIELDS as SCHEMA_CONFIG_RAW_FIELDS, + MIGRATION_0038_UPDATE_FIELDS as SCHEMA_CONFIG_FIELD_DESC, +) + +def update_0038_fields(apps): + """ + Update table-field schema entries for plain field names + (e.g., MIGRATION_0038_FIELDS). + """ + Discipline = apps.get_model('specify', 'Discipline') + for discipline in Discipline.objects.all(): + for table, fields in SCHEMA_CONFIG_RAW_FIELDS.items(): + for field_name in fields: + update_table_field_schema_config_with_defaults(table, discipline.id, field_name, apps) + +def revert_0038_fields(apps): + """ + Revert table-field entries for plain field names. + """ + for table, fields in SCHEMA_CONFIG_RAW_FIELDS.items(): + for field_name in fields: + revert_table_field_schema_config(table, field_name, apps) + +def update_0038_schema_config_field_desc(apps, schema_editor): + """ + Update field descriptions and display names using MIGRATION_0038_UPDATE_FIELDS + (tuple: (fieldName, newLabel, newDesc)). + """ + Splocalecontainer = apps.get_model('specify', 'Splocalecontainer') + Splocalecontaineritem = apps.get_model('specify', 'Splocalecontaineritem') + Splocaleitemstr = apps.get_model('specify', 'Splocaleitemstr') + + for table, fields in SCHEMA_CONFIG_FIELD_DESC.items(): + containers = Splocalecontainer.objects.filter(name=table.lower()) + for container in containers: + for (field_name, new_name, new_desc) in fields: + items = Splocalecontaineritem.objects.filter( + container=container, + name=field_name.lower() + ) + for item in items: + item.ishidden = True + item.save() + desc_str = Splocaleitemstr.objects.filter(itemdesc_id=item.id).first() + name_str = Splocaleitemstr.objects.filter(itemname_id=item.id).first() + if not desc_str or not name_str: + continue + desc_str.text = new_desc + desc_str.save() + name_str.text = new_name + name_str.save() + +def revert_0038_schema_config_field_desc(apps, schema_editor): + """ + Revert the field name/description updates. + """ + Splocalecontainer = apps.get_model('specify', 'Splocalecontainer') + Splocalecontaineritem = apps.get_model('specify', 'Splocalecontaineritem') + + for table, fields in SCHEMA_CONFIG_FIELD_DESC.items(): + containers = Splocalecontainer.objects.filter(name=table.lower()) + for container in containers: + for (field_name, _, _) in fields: + items = Splocalecontaineritem.objects.filter( + container=container, + name=field_name.lower() + ) + for item in items: + # If needed, reset ishidden or revert text + pass + +def consolidated_0038_forward(apps, schema_editor): + update_0038_fields(apps) + update_0038_schema_config_field_desc(apps, schema_editor) + +def consolidated_0038_backward(apps, schema_editor): + revert_0038_schema_config_field_desc(apps, schema_editor) + revert_0038_fields(apps) + +class Migration(migrations.Migration): + + dependencies = [ + ('specify', '0038_make_countonly_default_false'), + ] + + operations = [ + migrations.AddField( + model_name='gift', + name='agent1', + field=models.ForeignKey(db_column='Agent1ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='gift', + name='agent2', + field=models.ForeignKey(db_column='Agent2ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='gift', + name='agent3', + field=models.ForeignKey(db_column='Agent3ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='gift', + name='agent4', + field=models.ForeignKey(db_column='Agent4ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='gift', + name='agent5', + field=models.ForeignKey(db_column='Agent5ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='loan', + name='agent1', + field=models.ForeignKey(db_column='Agent1ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='loan', + name='agent2', + field=models.ForeignKey(db_column='Agent2ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='loan', + name='agent3', + field=models.ForeignKey(db_column='Agent3ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='loan', + name='agent4', + field=models.ForeignKey(db_column='Agent4ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.AddField( + model_name='loan', + name='agent5', + field=models.ForeignKey(db_column='Agent5ID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent'), + ), + migrations.RunPython( + consolidated_0038_forward, + consolidated_0038_backward, + atomic=True, + ), + ] \ No newline at end of file diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index f0a74a68cc2..d4e667a46a3 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -3825,6 +3825,11 @@ class Gift(models.Model): # Relationships: Many-to-One addressofrecord = models.ForeignKey('AddressOfRecord', db_column='AddressOfRecordID', related_name='+', null=True, on_delete=protect_with_blockers) + agent1 = models.ForeignKey('Agent', db_column='Agent1ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent2 = models.ForeignKey('Agent', db_column='Agent2ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent3 = models.ForeignKey('Agent', db_column='Agent3ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent4 = models.ForeignKey('Agent', db_column='Agent4ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent5 = models.ForeignKey('Agent', db_column='Agent5ID', related_name='+', null=True, on_delete=protect_with_blockers) createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) deaccession = models.ForeignKey('Deaccession', db_column='DeaccessionID', related_name='gifts', null=True, on_delete=protect_with_blockers) discipline = models.ForeignKey('Discipline', db_column='DisciplineID', related_name='+', null=False, on_delete=protect_with_blockers) @@ -4322,6 +4327,11 @@ class Loan(models.Model): # Relationships: Many-to-One addressofrecord = models.ForeignKey('AddressOfRecord', db_column='AddressOfRecordID', related_name='loans', null=True, on_delete=protect_with_blockers) + agent1 = models.ForeignKey('Agent', db_column='Agent1ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent2 = models.ForeignKey('Agent', db_column='Agent2ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent3 = models.ForeignKey('Agent', db_column='Agent3ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent4 = models.ForeignKey('Agent', db_column='Agent4ID', related_name='+', null=True, on_delete=protect_with_blockers) + agent5 = models.ForeignKey('Agent', db_column='Agent5ID', related_name='+', null=True, on_delete=protect_with_blockers) createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) discipline = models.ForeignKey('Discipline', db_column='DisciplineID', related_name='+', null=False, on_delete=protect_with_blockers) division = models.ForeignKey('Division', db_column='DivisionID', related_name='+', null=True, on_delete=protect_with_blockers)