Skip to content

Compartmentalize migration code#8223

Open
CarolineDenis wants to merge 48 commits into
issue-8124-8126-fix-7660-7682-2-after-review-2from
issue-8058
Open

Compartmentalize migration code#8223
CarolineDenis wants to merge 48 commits into
issue-8124-8126-fix-7660-7682-2-after-review-2from
issue-8058

Conversation

@CarolineDenis

@CarolineDenis CarolineDenis commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #8058

Warning

This PR affects database migrations. See migration testing instructions.

Checklist

  • Self-review the PR after opening it to make sure the changes look good and
    self-explanatory (or properly documented)
  • Add relevant issue to release milestone
  • Add pr to documentation list
  • Add automated tests
  • Add a reverse migration if a migration is present in the PR
  • Add migration function to
    def fix_schema_config(stdout: WriteToStdOut | None = None):

Testing instructions

Summary by CodeRabbit

Release Notes

  • Refactor

    • Reorganized internal migration utility imports across 20+ migration files for improved code structure and maintainability. Consolidated schema configuration helper functions into dedicated versioned modules with clearer separation of concerns.
  • Chores

    • Added schema deduplication utilities and enhanced schema reading/writing infrastructure for more robust data migration handling.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a377a39-edfc-43c9-b1d4-fc5056a02cdf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR splits the monolithic update_schema_config migration utility into focused modules: schema_reader.py, schema_writer.py, deduplication.py, utils.py, and a migration_helpers/ package containing ~20 per-migration helper_XXXX_*.py submodules. All existing Django migrations, management commands, and setup tools are updated to import directly from the new locations instead of the old usc alias.

Changes

migration_utils Module Split

Layer / File(s) Summary
Batch utilities and SchemaReader foundation
specifyweb/specify/migration_utils/utils.py, specifyweb/specify/migration_utils/schema_reader.py
utils.py adds batch_query/batch_iterable generators. schema_reader.py adds HIDDEN_FIELDS, cached discipline override loaders, string helpers (camel_to_spaced_title_case, datamodel_type_to_schematype), FieldSchemaConfig/FieldDefaults/TableDefaults types, bulk_create_splocaleitemstr_idempotent, and find_missing_schema_config_fields.
SchemaWriter: table/field create, revert, params, backfill
specifyweb/specify/migration_utils/schema_writer.py
Adds TableSchemaConfig/ContainerAttrs typed shapes, get_or_create_splocalecontainer, update_table_schema_config_with_defaults, revert_table_schema_config, update_table_field_schema_config_with_defaults, revert_table_field_schema_config, update_table_field_schema_config_params, and create_missing_schema_config_fields.
Deduplication routines
specifyweb/specify/migration_utils/deduplication.py
Adds deduplicate_schema_config_sql (raw SQL via temp table/ROW_NUMBER), deduplicate_splocalecontainers (ORM by timestampcreated), deduplicate_containeritems_and_strings (ORM RowNumber window), and deduplicate_schema_config_orm wrapper.
default_cots.py cleanup
specifyweb/specify/migration_utils/default_cots.py
Removes five helpers (DEFAULT_COG_TYPES, create_default_discipline_for_tree_defs, create_cogtype_type_picklist, create_cotype_picklist, set_discipline_for_taxon_treedefs); replaces them with fix_taxon_treedef_discipline_links using a Subquery bulk-update.
Per-migration helper modules 0002–0013
specifyweb/specify/migration_utils/migration_helpers/helper_0002_*.py, helper_0003_*.py, ..., helper_0013_*.py
Adds seven helper submodules defining MIGRATION_XXXX_* constants and forward/revert functions for geo table config, cotype picklist, stratigraphy/age picklist, COG type picklist wiring, relative age fields, cojo field, and parentCog field.
Per-migration helper modules 0015–0027
specifyweb/specify/migration_utils/migration_helpers/helper_0015_*.pyhelper_0027_*.py
Adds seven helper submodules for age version schema config, schema-type/caption fixes, catalog number localization, tectonic unit field, hidden geo flags, schema text/hidden property updates (with inline deduplication), storage unique ID, and CollectionObject children.
Per-migration helper modules 0029–0042
specifyweb/specify/migration_utils/migration_helpers/helper_0029_*.pyhelper_0042_*.py
Adds eight helper submodules for parentco removal/component fields, gift quantities, paleo description, accession date fields, version-required flag toggling, loan/gift agent fields, component schema config with picklist wiring, and discipline type picklist creation.
migration_helpers/__init__.py and sp7_schemaconfig.py removal
specifyweb/specify/migration_utils/migration_helpers/__init__.py, specifyweb/specify/migration_utils/sp7_schemaconfig.py
__init__.py re-exports all MIGRATION_XXXX_* constants from each helper submodule at the package level. sp7_schemaconfig.py is emptied, removing all previously centralized constants.
Django migrations rewired to direct imports
specifyweb/specify/migrations/0002_geo.py, 0003_cotype_picklist.py, ..., 0042_discipline_type_picklist.py, specifyweb/backend/workbench/migrations/0007_spdatasetattachment.py, specifyweb/backend/permissions/migrations/0008_attachment_import_role.py
All migration files replace import update_schema_config as usc with direct named imports from the corresponding helper_XXXX_* module; migration logic and RunPython operation ordering are unchanged. Migration 0039 additionally renames consolidated_0038_forward/backward to consolidated_0039_forward/backward.
Management commands and setup tool
specifyweb/specify/management/commands/run_key_migration_functions.py, specifyweb/specify/management/commands/sync_schema_config_fields.py, specifyweb/backend/setup_tool/schema_defaults.py
run_key_migration_functions.py imports helpers directly from their helper_XXXX modules and calls deduplicate_schema_config_orm directly. sync_schema_config_fields.py aliases migration_helpers as update_schema. schema_defaults.py imports from schema_writer.

Possibly related issues

  • Clean migration code #8058 (Clean migration code): This PR directly implements the proposed refactoring approach — splitting update_schema_config.py into SchemaReader.py, SchemaWriter.py, MigrationHelpers.py, and Deduplication.py — as described in that issue.
  • Use iterators to evaluate large Django QuerySets #7864: The new specifyweb/specify/migration_utils/utils.py introduces batch_query() using Django's .iterator() method, which addresses the memory-efficiency concern raised in that issue.

Possibly related PRs

  • specify/specify7#6308: Modifies run_key_migration_functions.py in the same file this PR rewires its schema-config helper imports and fix_schema_config function list.
  • specify/specify7#8109: Overlaps with this PR's changes to run_key_migration_functions.py, specifically the fix_schema_config helper pipeline and funcs list wiring.

Suggested reviewers

  • grantfitzsimmons
  • emenslin
  • acwhite211
🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Automatic Tests ⚠️ Warning PR checklist shows "Add automated tests" is unchecked. PR adds ~2000+ lines of new functionality across schema_reader.py, schema_writer.py, deduplication.py, migration helpers, and utilities withou... Add automated tests for new functions in schema_reader.py, schema_writer.py, deduplication.py, and utils.py modules. Follow existing test patterns in specifyweb/specify/tests/test_utils/.
Testing Instructions ⚠️ Warning Testing instructions section is empty; only placeholder comments present. For a major database migration refactoring, specific testing steps are required. Add detailed testing instructions covering: migration execution verification, schema config integrity checks, and any automated test execution steps.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR, which is to refactor and compartmentalize migration code by splitting monolithic files into separate modules.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #8058 by implementing the first increment of the proposed refactoring: creating SchemaReader, SchemaWriter, MigrationHelpers, and Deduplication modules, plus updating all migration files to use the new compartmentalized structure.
Out of Scope Changes check ✅ Passed All changes are directly related to the migration code compartmentalization objective. The PR includes migration helper files, schema utilities, and updates to existing migrations to use the new modular structure, with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8058

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@CarolineDenis

Copy link
Copy Markdown
Contributor Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specifyweb/specify/migration_utils/Deduplication.py`:
- Around line 83-92: The duplicate container detection in the annotated subquery
only matches containers with a strictly earlier timestampcreated value, which
allows containers with identical timestamps to evade deduplication. Modify the
filter conditions within the Exists subquery to also match containers where the
timestampcreated values are equal but the primary key (id) is smaller than the
outer reference's id, using OuterRef('id') as the tie-breaker. This can be
accomplished by adding an OR condition using Q objects or by adding an
additional filter clause that handles the equal timestamp case with id as the
deterministic ordering criterion.

In `@specifyweb/specify/migration_utils/MigrationHelpers.py`:
- Around line 1-16: The MigrationHelpers module is imported by
sync_schema_config_fields which calls find_missing_schema_config_fields() and
create_missing_schema_config_fields() as module attributes, but these functions
are not currently imported or re-exported in this file. Add imports for
find_missing_schema_config_fields and create_missing_schema_config_fields from
the appropriate source modules (SchemaReader and/or SchemaWriter) to the
existing import statements in MigrationHelpers.py so these functions become
accessible when the module is imported.

In `@specifyweb/specify/migration_utils/SchemaReader.py`:
- Line 18: Break the circular import between SchemaReader and SchemaWriter by
moving the FieldDefaults class definition from SchemaWriter to SchemaReader.
First, locate the FieldDefaults class in SchemaWriter (note there are duplicate
definitions at lines 24 and 145 that should be consolidated into a single
definition) and move it to SchemaReader where it is actually needed for the
TableDefaults TypedDict definition. Then update SchemaWriter to import
FieldDefaults from SchemaReader instead of defining it locally, and remove both
duplicate definitions from SchemaWriter.

In `@specifyweb/specify/migration_utils/SchemaWriter.py`:
- Around line 72-74: The revert helper functions filter using raw
caller-provided table_name values, but the forward helpers write schema
containers with lower-case names, causing revert filters to miss created rows.
At the anchor location in lines 72-74, the forward write uses
table_config.name.lower(). Apply the same normalization to revert filter
operations at lines 134-135 and 281-282 by changing filters that reference
table_name to use the lower-cased version instead. Additionally, add schematype:
0 filtering to all three revert locations (72-74, 134-135, and 281-282) since
forward helpers only create schematype=0 records, ensuring reverts only target
the correct records they created.
- Around line 313-331: The update_table_field_schema_config_params() function is
creating Splocalecontainer objects when they don't exist but then returns early
if the field item is missing, leaving incomplete containers without proper
labels/items that interfere with later sync operations. Remove the container
creation logic from this function (around the Splocalecontainer.objects.create
call) and instead return early when sp_local_container is None, or delegate
container creation to a full create helper that properly initializes all
container attributes. This applies to two locations in the file as noted in the
consolidated sites.
- Around line 216-227: The defaults for `ishidden`, `isrequired`, and
`picklistname` are only being applied during field creation, but existing/reused
schema items are not being updated with these values. Ensure that when
processing existing field items (not just newly created ones), these default
values from the defaults dictionary are explicitly applied to update the
corresponding field properties. This requires identifying where field items are
persisted back to the schema and ensuring that the values derived from
defaults.get('ishidden', field_hidden), defaults.get('isrequired',
field_required), and defaults.get('picklistname', picklist_name) are actually
assigned to the existing field object in addition to being applied during
creation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 878aedba-067e-4017-a764-696da4e5dd9f

📥 Commits

Reviewing files that changed from the base of the PR and between 3d178cf and a2c9f1c.

📒 Files selected for processing (31)
  • specifyweb/backend/setup_tool/schema_defaults.py
  • specifyweb/backend/workbench/migrations/0007_spdatasetattachment.py
  • specifyweb/specify/management/commands/run_key_migration_functions.py
  • specifyweb/specify/management/commands/sync_schema_config_fields.py
  • specifyweb/specify/migration_utils/Deduplication.py
  • specifyweb/specify/migration_utils/MigrationHelpers.py
  • specifyweb/specify/migration_utils/SchemaReader.py
  • specifyweb/specify/migration_utils/SchemaWriter.py
  • specifyweb/specify/migrations/0002_geo.py
  • specifyweb/specify/migrations/0003_cotype_picklist.py
  • specifyweb/specify/migrations/0004_stratigraphy_age.py
  • specifyweb/specify/migrations/0007_schema_config_update.py
  • specifyweb/specify/migrations/0008_ageCitations_fix.py
  • specifyweb/specify/migrations/0012_add_cojo_to_schema_config.py
  • specifyweb/specify/migrations/0013_collectionobjectgroup_parentcog.py
  • specifyweb/specify/migrations/0015_add_version_to_ages.py
  • specifyweb/specify/migrations/0017_schemaconfig_fixes.py
  • specifyweb/specify/migrations/0018_cot_catnum_schema.py
  • specifyweb/specify/migrations/0020_add_tectonicunit_to_pc_in_schema_config.py
  • specifyweb/specify/migrations/0021_update_hidden_geo_tables.py
  • specifyweb/specify/migrations/0023_update_schema_config_text.py
  • specifyweb/specify/migrations/0024_add_uniqueIdentifier_storage.py
  • specifyweb/specify/migrations/0027_CO_children.py
  • specifyweb/specify/migrations/0029_remove_collectionobject_parentco.py
  • specifyweb/specify/migrations/0032_add_quantities_gift.py
  • specifyweb/specify/migrations/0033_update_paleo_desc.py
  • specifyweb/specify/migrations/0034_accession_date_fields.py
  • specifyweb/specify/migrations/0035_version_required.py
  • specifyweb/specify/migrations/0039_agent_fields_for_loan_and_gift.py
  • specifyweb/specify/migrations/0040_components.py
  • specifyweb/specify/migrations/0042_discipline_type_picklist.py

Comment thread specifyweb/specify/migration_utils/deduplication.py
Comment thread specifyweb/specify/migration_utils/MigrationHelpers.py Outdated
Comment thread specifyweb/specify/migration_utils/SchemaReader.py Outdated
Comment thread specifyweb/specify/migration_utils/schema_writer.py
Comment thread specifyweb/specify/migration_utils/schema_writer.py
Comment thread specifyweb/specify/migration_utils/schema_writer.py Outdated
@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board Jun 15, 2026
@CarolineDenis CarolineDenis changed the title Issue 8058 Compartmentalize migration code Jun 15, 2026
@CarolineDenis CarolineDenis linked an issue Jun 16, 2026 that may be closed by this pull request
@melton-jason

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (2)
specifyweb/specify/migrations/0007_schema_config_update.py (1)

19-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split the long import statement for better readability.

This import line is extremely long and difficult to read. Consider using parentheses to split it across multiple lines following PEP 8 style guidelines.

♻️ Proposed refactor to improve readability
-from specifyweb.specify.migration_utils.migration_helpers.helper_0007_schema_config_update import create_cogtype_picklist, revert_cog_type_fields, revert_cogtype_picklist, revert_cogtype_splocalecontaineritem, revert_cogtype_type_splocalecontaineritem, revert_systemcogtypes_picklist, update_cog_type_fields, update_cogtype_splocalecontaineritem, update_cogtype_type_splocalecontaineritem, update_systemcogtypes_picklist
+from specifyweb.specify.migration_utils.migration_helpers.helper_0007_schema_config_update import (
+    create_cogtype_picklist,
+    revert_cog_type_fields,
+    revert_cogtype_picklist,
+    revert_cogtype_splocalecontaineritem,
+    revert_cogtype_type_splocalecontaineritem,
+    revert_systemcogtypes_picklist,
+    update_cog_type_fields,
+    update_cogtype_splocalecontaineritem,
+    update_cogtype_type_splocalecontaineritem,
+    update_systemcogtypes_picklist,
+)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/specify/migrations/0007_schema_config_update.py` at line 19, The
import statement that imports create_cogtype_picklist, revert_cog_type_fields,
revert_cogtype_picklist, revert_cogtype_splocalecontaineritem,
revert_cogtype_type_splocalecontaineritem, revert_systemcogtypes_picklist,
update_cog_type_fields, update_cogtype_splocalecontaineritem,
update_cogtype_type_splocalecontaineritem, and update_systemcogtypes_picklist
from the migration helper module is too long and difficult to read. Refactor
this import statement by wrapping it in parentheses and splitting it across
multiple lines, placing each imported function on its own line or grouping
related imports together, to comply with PEP 8 style guidelines for improved
readability.
specifyweb/specify/migrations/0042_discipline_type_picklist.py (1)

3-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider splitting the import statement for consistency.

While not as long as some other imports in this refactoring, splitting this import across multiple lines would improve consistency and readability across the migration files.

♻️ Proposed refactor for consistency
-from specifyweb.specify.migration_utils.migration_helpers.helper_0042_discipline_type_picklist import create_discipline_type_picklist, revert_discipline_type_picklist, revert_discipline_type_splocalecontaineritem, update_discipline_type_splocalecontaineritem
+from specifyweb.specify.migration_utils.migration_helpers.helper_0042_discipline_type_picklist import (
+    create_discipline_type_picklist,
+    revert_discipline_type_picklist,
+    revert_discipline_type_splocalecontaineritem,
+    update_discipline_type_splocalecontaineritem,
+)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/specify/migrations/0042_discipline_type_picklist.py` at line 3,
The import statement in the migration file is currently a single long line
importing multiple functions (create_discipline_type_picklist,
revert_discipline_type_picklist, revert_discipline_type_splocalecontaineritem,
update_discipline_type_splocalecontaineritem) from the
helper_0042_discipline_type_picklist module. Split this import across multiple
lines for improved readability and consistency with other migration files. Each
imported function or a logical grouping of functions should be on its own line.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specifyweb/backend/permissions/migrations/0008_attachment_import_role.py`:
- Around line 88-91: The migrations.RunPython operation for
add_attachment_import_role needs to be made reversible by providing a reverse
function. Create a new function that reverses the changes made by
add_attachment_import_role, which should delete the "Bulk Attachment Import"
LibraryRole and its associated policies. Then update the migrations.RunPython
call to pass both the forward function (add_attachment_import_role) and the new
reverse function as arguments, in that order, to make the migration reversible.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0002_schema_config_update.py`:
- Line 22: In the UserRole tuple definition that contains the schema
description, there is a typo in the description string where "ecify users"
should be "Specify users". Locate the UserRole tuple entry and correct the
description text to read "Records roles associated with Specify users." instead
of "Records roles associated with ecify users."

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0008_schema_config_update.py`:
- Around line 20-24: The function `revert_relative_age_fields` has an
indentation error where the for loops on lines 22-24 are over-indented relative
to the function body. Fix this by reducing the indentation of both the outer for
loop (iterating over MIGRATION_0008_FIELDS.items()) and the inner for loop
(iterating over fields) to align with the comment above them. Both loops should
be indented exactly one level from the function definition to be at the proper
function body indentation level.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0017_schemaconfig_fixes.py`:
- Around line 38-43: The migration helper currently skips schema-config updates
when the table does not exist in the latest datamodel state (when
datamodel.get_table(table_name) returns None), but this approach is
non-deterministic for historical migrations with renamed or removed tables and
leaves stale entries uncorrected. Instead of checking table existence against
the current datamodel, refactor the logic to drive schema-config updates
directly from existing Splocalecontainer or Splocalecontaineritem rows in the
database (or use frozen migration metadata), ensuring that historical data is
processed regardless of whether the table still exists in the latest datamodel
state. Apply this refactoring to both code sections mentioned (lines 38-43 and
74-79).

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0021_update_hidden_geo_tables.py`:
- Around line 49-53: The reverse migration in the update_hidden_geo_tables
helper unconditionally sets ishidden=False on
Splocalecontaineritem.objects.filter(), which loses the original state of
records that were already hidden. Either store the original ishidden values for
each affected row during the forward migration and restore them in the reverse
migration, or make the reverse migration a no-op (empty operation) with a clear
comment explaining that this migration is irreversible and data about prior
hidden states cannot be recovered.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0023_update_schema_config_text.py`:
- Around line 191-193: The duplicate_items queryset selection is
non-deterministic because it uses .first() without any ordering, which means a
random duplicate item could be kept each time. Add an .order_by() clause to the
duplicate_items queryset before calling .first() on it to ensure the same item
is consistently selected. This will make the retention of item_to_keep
deterministic and ensure consistent metadata is preserved across migrations.
- Around line 206-208: The reverse migration query for
Splocalecontainer.objects.filter() is not matching the forward migration scope.
Add a schematype filter to the query on Splocalecontainer.objects.filter() to
include schematype=0, matching the constraint applied in the forward migration
path. This ensures the reverse migration only targets the same containers that
were modified by the forward migration, preventing unhiding of rows that were
never modified by this migration.
- Line 72: In the CollectionObjectGroup field list, remove the leading
whitespace from the field name ' text3' so it becomes 'text3' without the space.
The leading whitespace in the field name will prevent proper matching against
actual schema field names during the migration, causing the hidden-flag logic to
skip that field entirely.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0027_CO_children.py`:
- Around line 75-79: The for loop iterating over fields is receiving tuple
objects instead of unpacking them, causing the
Splocalecontaineritem.objects.filter call with name=field_name to compare
against a tuple value instead of the actual field name string. Modify the for
loop to properly unpack the tuple from fields, extracting only the field name
component that should be used in the filter's name parameter.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0033_update_paleo_desc.py`:
- Line 16: The update operation on
Splocaleitemstr.objects.filter(containerdesc__in=containers).update(text=table_desc)
is overwriting localized descriptions across all languages instead of updating
only the intended language. Add an additional filter condition to the queryset
to specify which language or locale should be updated, ensuring that
translations in other languages are not overwritten. This will require filtering
by a language identifier or locale field before calling the update method.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0042_discipline_type_picklist.py`:
- Around line 59-63: The revert_discipline_type_picklist function currently
deletes all Picklist objects with the name matching
DISCIPLINE_TYPE_PICKLIST_NAME, which can inadvertently remove user-created
picklists with the same name. Modify the filter criteria in the
Picklist.objects.filter() call to include additional attributes that uniquely
identify picklists created by this migration (such as system ownership or
application-specific markers) to ensure only migration-owned picklists are
deleted during rollback.
- Around line 67-86: The rollback function
`revert_discipline_type_splocalecontaineritem` is incomplete because it only
resets the `picklistname` field to None but fails to reset the `isrequired`
field that was set to True in the forward migration. Update the `.update()` call
in `revert_discipline_type_splocalecontaineritem` to also include
`isrequired=None` alongside the existing `picklistname=None` to fully restore
the original state before the migration.

In `@specifyweb/specify/migration_utils/schema_writer.py`:
- Around line 40-94: The function get_or_create_splocalecontainer lowercases
resolved_container_attrs['name'] on line 57, and then passes this lowercased
value to camel_to_spaced_title_case(uncapitilize()) at lines 84 and 89 for label
generation. Since the camelCase detection regex only recognizes uppercase
letters, lowercased input like "collectionobjecttype" cannot be properly split
into words. Preserve the original casing of the name before the lowercasing
operation by storing it in a separate variable, then use that original-cased
value when calling camel_to_spaced_title_case() for the table_label and
table_description defaults, while keeping the lowercased version only for the
database operations (filter and create).

In `@specifyweb/specify/migrations/0029_remove_collectionobject_parentco.py`:
- Line 6: Remove the unused import alias from the migration_helpers import
statement. The alias usc is not referenced anywhere in the migration file and
only clutters the code. Simply remove the "as usc" portion from the import
statement so that migration_helpers is imported directly without an alias,
keeping the import clean and focused on what is actually being used.

---

Nitpick comments:
In `@specifyweb/specify/migrations/0007_schema_config_update.py`:
- Line 19: The import statement that imports create_cogtype_picklist,
revert_cog_type_fields, revert_cogtype_picklist,
revert_cogtype_splocalecontaineritem, revert_cogtype_type_splocalecontaineritem,
revert_systemcogtypes_picklist, update_cog_type_fields,
update_cogtype_splocalecontaineritem, update_cogtype_type_splocalecontaineritem,
and update_systemcogtypes_picklist from the migration helper module is too long
and difficult to read. Refactor this import statement by wrapping it in
parentheses and splitting it across multiple lines, placing each imported
function on its own line or grouping related imports together, to comply with
PEP 8 style guidelines for improved readability.

In `@specifyweb/specify/migrations/0042_discipline_type_picklist.py`:
- Line 3: The import statement in the migration file is currently a single long
line importing multiple functions (create_discipline_type_picklist,
revert_discipline_type_picklist, revert_discipline_type_splocalecontaineritem,
update_discipline_type_splocalecontaineritem) from the
helper_0042_discipline_type_picklist module. Split this import across multiple
lines for improved readability and consistency with other migration files. Each
imported function or a logical grouping of functions should be on its own line.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ba775b37-0d55-45b6-961a-e15dffed728a

📥 Commits

Reviewing files that changed from the base of the PR and between a2c9f1c and 7f9a8e2.

📒 Files selected for processing (58)
  • specifyweb/backend/permissions/migrations/0008_attachment_import_role.py
  • specifyweb/backend/setup_tool/schema_defaults.py
  • specifyweb/backend/workbench/migrations/0007_spdatasetattachment.py
  • specifyweb/specify/management/commands/run_key_migration_functions.py
  • specifyweb/specify/management/commands/sync_schema_config_fields.py
  • specifyweb/specify/migration_utils/deduplication.py
  • specifyweb/specify/migration_utils/default_cots.py
  • specifyweb/specify/migration_utils/migration_helpers/__init__.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0002_schema_config_update.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0003_cotype_picklist.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0004_stratigraphy_age.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0007_schema_config_update.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0008_schema_config_update.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0012_add_cojo_to_schema_config.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0013_collectionobjectgroup_parentcog.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0015_add_version_to_ages.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0017_schemaconfig_fixes.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0018_cot_catnum_schema.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0020_add_tectonicunit_to_pc_in_schema_config.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0021_update_hidden_geo_tables.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0023_update_schema_config_text.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0024_add_uniqueIdentifier_storage.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0027_CO_children.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0029_remove_collectionobject_parentco.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0032_add_quantities_gift.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0033_update_paleo_desc.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0034_accession_date_fields.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0035_version_required.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0039_agent_fields_for_loan_and_gift.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0040_components.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0042_discipline_type_picklist.py
  • specifyweb/specify/migration_utils/schema_reader.py
  • specifyweb/specify/migration_utils/schema_writer.py
  • specifyweb/specify/migration_utils/sp7_schemaconfig.py
  • specifyweb/specify/migration_utils/utils.py
  • specifyweb/specify/migrations/0002_geo.py
  • specifyweb/specify/migrations/0003_cotype_picklist.py
  • specifyweb/specify/migrations/0004_stratigraphy_age.py
  • specifyweb/specify/migrations/0007_schema_config_update.py
  • specifyweb/specify/migrations/0008_ageCitations_fix.py
  • specifyweb/specify/migrations/0012_add_cojo_to_schema_config.py
  • specifyweb/specify/migrations/0013_collectionobjectgroup_parentcog.py
  • specifyweb/specify/migrations/0015_add_version_to_ages.py
  • specifyweb/specify/migrations/0017_schemaconfig_fixes.py
  • specifyweb/specify/migrations/0018_cot_catnum_schema.py
  • specifyweb/specify/migrations/0020_add_tectonicunit_to_pc_in_schema_config.py
  • specifyweb/specify/migrations/0021_update_hidden_geo_tables.py
  • specifyweb/specify/migrations/0023_update_schema_config_text.py
  • specifyweb/specify/migrations/0024_add_uniqueIdentifier_storage.py
  • specifyweb/specify/migrations/0027_CO_children.py
  • specifyweb/specify/migrations/0029_remove_collectionobject_parentco.py
  • specifyweb/specify/migrations/0032_add_quantities_gift.py
  • specifyweb/specify/migrations/0033_update_paleo_desc.py
  • specifyweb/specify/migrations/0034_accession_date_fields.py
  • specifyweb/specify/migrations/0035_version_required.py
  • specifyweb/specify/migrations/0039_agent_fields_for_loan_and_gift.py
  • specifyweb/specify/migrations/0040_components.py
  • specifyweb/specify/migrations/0042_discipline_type_picklist.py
💤 Files with no reviewable changes (1)
  • specifyweb/specify/migration_utils/sp7_schemaconfig.py
✅ Files skipped from review due to trivial changes (4)
  • specifyweb/specify/management/commands/sync_schema_config_fields.py
  • specifyweb/backend/setup_tool/schema_defaults.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0039_agent_fields_for_loan_and_gift.py
  • specifyweb/backend/workbench/migrations/0007_spdatasetattachment.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • specifyweb/specify/migrations/0024_add_uniqueIdentifier_storage.py
  • specifyweb/specify/migrations/0027_CO_children.py
  • specifyweb/specify/migrations/0017_schemaconfig_fixes.py
  • specifyweb/specify/migrations/0034_accession_date_fields.py
  • specifyweb/specify/migrations/0015_add_version_to_ages.py

Comment on lines +59 to +63
def revert_discipline_type_picklist(apps):
Picklist = apps.get_model('specify', 'Picklist')

Picklist.objects.filter(name=DISCIPLINE_TYPE_PICKLIST_NAME).delete()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Scope rollback deletion to migration-owned picklists.

Line 62 currently deletes every Picklist named DisciplineType. That can remove user-created rows with the same name. Restrict the filter to the attributes this migration creates.

Proposed fix
 def revert_discipline_type_picklist(apps):
     Picklist = apps.get_model('specify', 'Picklist')

-    Picklist.objects.filter(name=DISCIPLINE_TYPE_PICKLIST_NAME).delete()
+    Picklist.objects.filter(
+        name=DISCIPLINE_TYPE_PICKLIST_NAME,
+        type=0,
+        issystem=True,
+        readonly=True,
+        sizelimit=-1,
+        sorttype=1,
+    ).delete()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0042_discipline_type_picklist.py`
around lines 59 - 63, The revert_discipline_type_picklist function currently
deletes all Picklist objects with the name matching
DISCIPLINE_TYPE_PICKLIST_NAME, which can inadvertently remove user-created
picklists with the same name. Modify the filter criteria in the
Picklist.objects.filter() call to include additional attributes that uniquely
identify picklists created by this migration (such as system ownership or
application-specific markers) to ensure only migration-owned picklists are
deleted during rollback.

Comment on lines +67 to +86
Splocalecontaineritem.objects.filter(
container__name="discipline",
container__schematype=0,
name="type",
).update(
picklistname=DISCIPLINE_TYPE_PICKLIST_NAME,
isrequired=True
)

def revert_discipline_type_splocalecontaineritem(apps):
Splocalecontaineritem = apps.get_model("specify", "Splocalecontaineritem")

Splocalecontaineritem.objects.filter(
container__name="discipline",
picklistname=DISCIPLINE_TYPE_PICKLIST_NAME,
container__schematype=0,
name="type",
).update(
picklistname=None
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect current helper behavior
cat -n specifyweb/specify/migration_utils/migration_helpers/helper_0042_discipline_type_picklist.py | sed -n '60,90p'

# 2) Inspect migration wiring for 0042
cat -n specifyweb/specify/migrations/0042_discipline_type_picklist.py | sed -n '1,80p'

# 3) Find prior writes to discipline/type schema-config requiredness to infer intended baseline
rg -n --type=py "container__name=['\"]discipline['\"].*name=['\"]type['\"]|isrequired" \
  specifyweb/specify/migrations \
  specifyweb/specify/migration_utils/migration_helpers

Repository: specify/specify7

Length of output: 4844


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find when discipline/type schema item was created and its initial isrequired state
# Search migration 0041 (the dependency) and earlier helpers for discipline/type
rg -n "discipline.*type|container.*name.*discipline" \
  specifyweb/specify/migrations/0041_add_missing_schema_after_reorganization.py \
  specifyweb/specify/migration_utils/migration_helpers/helper_0041*.py 2>/dev/null || echo "0041 files not found or no matches"

# Check initial schema creation (migration 0001) for discipline table/fields
rg -n -A 5 -B 5 "name='discipline'" specifyweb/specify/migrations/0001_initial.py | head -100

# Look for any earlier helpers that might have set discipline/type configuration
rg -n "discipline.*type.*isrequired|Splocalecontaineritem.*discipline.*type" \
  specifyweb/specify/migration_utils/migration_helpers/ | head -30

Repository: specify/specify7

Length of output: 5494


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for Splocalecontaineritem creation/update for discipline container and type field
rg -n "Splocalecontaineritem" specifyweb/specify/migration_utils/migration_helpers/ | grep -i "discipline\|type" | head -50

# Look for container creation for discipline
rg -n "container.*discipline\|name.*discipline" specifyweb/specify/migration_utils/migration_helpers/ | head -50

# Inspect helper_0001 or the earliest helpers that set up schema config
ls -la specifyweb/specify/migration_utils/migration_helpers/ | grep helper_000 | head -10

Repository: specify/specify7

Length of output: 1470


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List all helpers to understand the sequence
ls -1 specifyweb/specify/migration_utils/migration_helpers/helper_*.py | sort

# Inspect the most relevant helpers (0017, 0018, 0035, 0040, 0041) for discipline/type config
for helper in helper_0017 helper_0018 helper_0035 helper_0040 helper_0041; do
  file="specifyweb/specify/migration_utils/migration_helpers/${helper}_*.py"
  echo "=== Searching in $helper ==="
  rg -n "discipline.*type|container.*name.*discipline" $file 2>/dev/null || echo "  (no matches)"
done

# Look for explicit creation of discipline container and its items
rg -n -B 3 -A 3 "container.*name.*=.*['\"]discipline['\"]" \
  specifyweb/specify/migration_utils/migration_helpers/ | head -100

Repository: specify/specify7

Length of output: 4319


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if discipline/type Splocalecontaineritem was ever created before migration 0042
# by looking at all migrations and helpers for any mention of discipline type creation
rg -n "discipline.*type\|type.*discipline" specifyweb/specify/migrations/ | grep -v "0042" | head -20

# Look for schema initialization that might create discipline container items
rg -n -B 5 -A 5 "Splocalecontaineritem.*create\|\.create.*container" \
  specifyweb/specify/migration_utils/migration_helpers/helper_0002*.py \
  2>/dev/null | head -80

# Check if there's any fixture or initial data that sets up discipline/type
find specifyweb -name "*.json" -o -name "fixtures*" 2>/dev/null | head -10

Repository: specify/specify7

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefcatollow pipefail

# Check if the discipline container itself is created in earlier helpers
rg -n "Splocalecontainer.*create\|container.*=.*create" \
  specifyweb/specify/migration_utils/migration_helpers/ --type=py | grep -v "0042" | head -20

# Look for any tests related to discipline schema config
find specifyweb -path "*/test*" -name "*.py" -exec grep -l "discipline.*type\|Splocalecontaineritem" {} \; | head -10

# Check the complete helper_0002 which seems to be a schema config update
wc -l specifyweb/specify/migration_utils/migration_helpers/helper_0002_schema_config_update.py

# Look at the beginning of helper_0002 to see if it deals with discipline
head -100 specifyweb/specify/migration_utils/migration_helpers/helper_0002_schema_config_update.py

Repository: specify/specify7

Length of output: 208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if the discipline container itself is created in earlier helpers
rg -n "Splocalecontainer.*create\|container.*=.*create" \
  specifyweb/specify/migration_utils/migration_helpers/ --type=py | grep -v "0042" | head -20

# Check the complete helper_0002 which seems to be a schema config update
wc -l specifyweb/specify/migration_utils/migration_helpers/helper_0002_schema_config_update.py

# Look at the beginning of helper_0002 to see if it deals with discipline
head -150 specifyweb/specify/migration_utils/migration_helpers/helper_0002_schema_config_update.py

Repository: specify/specify7

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Simple direct check - let's look at helper_0002
cat specifyweb/specify/migration_utils/migration_helpers/helper_0002_schema_config_update.py | head -200

Repository: specify/specify7

Length of output: 5368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for where discipline/type Splocalecontaineritem gets its initial value
# by looking at raw SQL or fixture data
find specifyweb -type f \( -name "*.sql" -o -name "*.json" -o -name "fixtures*" \) 2>/dev/null | head -20

# Check if there's a schema_writer or schema initialization function
rg -n "discipline" specifyweb/specify/migration_utils/schema_writer.py | head -20

# Look for when discipline schema items (splocalecontaineritems) were created
rg -n "schematype.*0\|Splocalecontainer.*discipline" specifyweb/specify/migration_utils/ --type=py | head -30

# Check if the field exists at all and what its initial isrequired value is
# by looking at helper_0001 for any schema init
grep -n "isrequired" specifyweb/specify/migrations/0001_initial.py | head -20

Repository: specify/specify7

Length of output: 1992


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the database schema definition (model definition in models.py)
# to see if Splocalecontaineritem.isrequired has a default value
rg -n -B 5 -A 5 "class Splocalecontaineritem" specifyweb/specify/models.py | head -50

# Look at the initial migration definition of Splocalecontaineritem
rg -n -B 10 -A 10 "name='splocalecontaineritem'" specifyweb/specify/migrations/0001_initial.py

Repository: specify/specify7

Length of output: 3037


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the full model definition for Splocalecontaineritem
rg -n -A 50 "class Splocalecontaineritem" specifyweb/specify/models.py | head -100

# Also look for the migration definition of isrequired field
rg -n "isrequired.*BooleanField\|isrequired.*null.*True" specifyweb/specify/migrations/0001_initial.py | grep -A 2 -B 2 "splocalecontaineritem"

Repository: specify/specify7

Length of output: 4026


Fix rollback asymmetry for isrequired on discipline.type.

Forward migration sets isrequired=True (line 73), but rollback does not reset isrequired (line 85 only clears picklistname). Since the field defaults to null=True, the field was initially None; rollback must reset it to None to restore the previous state.

Update the rollback to:

.update(
    picklistname=None,
    isrequired=None
)

This matches the pattern used in other schema migrations (e.g., helper_0007).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@specifyweb/specify/migration_utils/migration_helpers/helper_0042_discipline_type_picklist.py`
around lines 67 - 86, The rollback function
`revert_discipline_type_splocalecontaineritem` is incomplete because it only
resets the `picklistname` field to None but fails to reset the `isrequired`
field that was set to True in the forward migration. Update the `.update()` call
in `revert_discipline_type_splocalecontaineritem` to also include
`isrequired=None` alongside the existing `picklistname=None` to fully restore
the original state before the migration.

Comment on lines +40 to +94
def get_or_create_splocalecontainer(Splocalecontainer, Splocaleitemstr, table_label: str | None = None, table_description: str | None = None, **container_attrs: Unpack[ContainerAttrs]):
if "name" not in container_attrs.keys():
raise ValueError("Trying to create a SpLocaleContainer without a name!")

if "discipline_id" not in container_attrs.keys():
raise ValueError("Trying to create a SpLocaleContianer without a Discipline")

resolved_container_attrs: ContainerAttrs = {
"ishidden": False,
"issystem": False,
"schematype": 0,
"version": 0,
# The order of this unpacking matters
# If the defaults were specified in container_attrs, make sure to prioritize them over the defaults
**container_attrs
}

resolved_container_attrs['name'] = resolved_container_attrs['name'].lower()

sp_local_container = (
Splocalecontainer.objects.filter(
name=resolved_container_attrs['name'],
discipline_id=resolved_container_attrs['discipline_id'],
schematype=resolved_container_attrs['schematype']
)
.order_by('id')
.first()
)

if sp_local_container is not None:
# BUG?: Not sure if we want to handle also checking for and (if needed)
# creating the container strings here
return sp_local_container

sp_local_container = Splocalecontainer.objects.create(**resolved_container_attrs)

common_string_attrs = {
"language": "en",
"version": 0
}
container_string_rows = [
{
**common_string_attrs,
"containername": sp_local_container,
"text": table_label or camel_to_spaced_title_case(uncapitilize(resolved_container_attrs["name"]))
},
{
**common_string_attrs,
"containerdesc": sp_local_container,
"text": table_description or camel_to_spaced_title_case(uncapitilize(resolved_container_attrs["name"]))
}
]

bulk_create_splocaleitemstr_idempotent(Splocaleitemstr, container_string_rows)
return sp_local_container

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect camel_to_spaced_title_case to confirm it depends on camelCase boundaries
sed -n '113,145p' specifyweb/specify/migration_utils/schema_reader.py

Repository: specify/specify7

Length of output: 1234


🏁 Script executed:

#!/bin/bash
# Get the full function to verify lines 40-94
sed -n '40,94p' specifyweb/specify/migration_utils/schema_writer.py

Repository: specify/specify7

Length of output: 2288


🏁 Script executed:

#!/bin/bash
# Check the field updater calls mentioned at lines 243-248 and 369-374
sed -n '243,248p' specifyweb/specify/migration_utils/schema_writer.py
echo "---"
sed -n '369,374p' specifyweb/specify/migration_utils/schema_writer.py

Repository: specify/specify7

Length of output: 536


🏁 Script executed:

#!/bin/bash
# Search for all calls to get_or_create_splocalecontainer to see how it's used
rg 'get_or_create_splocalecontainer' specifyweb/specify/migration_utils/schema_writer.py -A 5 -B 2

Repository: specify/specify7

Length of output: 1170


🏁 Script executed:

#!/bin/bash
# Search entire file for all calls to get_or_create_splocalecontainer
rg 'get_or_create_splocalecontainer' specifyweb/specify/migration_utils/schema_writer.py --count

Repository: specify/specify7

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Get complete context around all calls to ensure we've found them all
grep -n 'get_or_create_splocalecontainer' specifyweb/specify/migration_utils/schema_writer.py

Repository: specify/specify7

Length of output: 467


🏁 Script executed:

#!/bin/bash
# Check the function signature and see what table_label/table_description are used for
# and verify the logic of uncapitilize
sed -n '1,30p' specifyweb/specify/migration_utils/schema_reader.py | head -20
sed -n '135,145p' specifyweb/specify/migration_utils/schema_reader.py

Repository: specify/specify7

Length of output: 814


Auto-generated container labels lose camelCase word boundaries.

resolved_container_attrs['name'] is lowercased at line 57, and that lowercased value is then fed to camel_to_spaced_title_case(uncapitilize(...)) at lines 84 and 89. Since the regex in camel_to_spaced_title_case detects only uppercase letters (r"(?<!^)(?=[A-Z])"), an all-lowercase input cannot be split into words (e.g., collectionobjecttypeCollectionobjecttype instead of Collection Object Type).

The field-level updaters at lines 243 and 369 call this helper with name=table.name.lower() and no table_label, so newly created containers receive mis-formatted labels. Preserve the original casing for label generation by storing it before lowercasing, and let the function apply lowercasing only for database storage and lookups.

Proposed fix
-    resolved_container_attrs['name'] = resolved_container_attrs['name'].lower()
+    original_name = resolved_container_attrs['name']
+    resolved_container_attrs['name'] = original_name.lower()
@@
-            "text": table_label or camel_to_spaced_title_case(uncapitilize(resolved_container_attrs["name"]))
+            "text": table_label or camel_to_spaced_title_case(uncapitilize(original_name))
@@
-            "text": table_description or camel_to_spaced_title_case(uncapitilize(resolved_container_attrs["name"]))
+            "text": table_description or camel_to_spaced_title_case(uncapitilize(original_name))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/specify/migration_utils/schema_writer.py` around lines 40 - 94,
The function get_or_create_splocalecontainer lowercases
resolved_container_attrs['name'] on line 57, and then passes this lowercased
value to camel_to_spaced_title_case(uncapitilize()) at lines 84 and 89 for label
generation. Since the camelCase detection regex only recognizes uppercase
letters, lowercased input like "collectionobjecttype" cannot be properly split
into words. Preserve the original casing of the name before the lowercasing
operation by storing it in a separate variable, then use that original-cased
value when calling camel_to_spaced_title_case() for the table_label and
table_description defaults, while keeping the lowercased version only for the
database operations (filter and create).

Comment thread specifyweb/specify/migrations/0029_remove_collectionobject_parentco.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Dev Attention Needed

Development

Successfully merging this pull request may close these issues.

Clean migration code

2 participants