Skip to content

Issue 8101 fix 6266 after review#8102

Open
CarolineDenis wants to merge 20 commits into
mainfrom
issue-8101-fix-6266-after-review
Open

Issue 8101 fix 6266 after review#8102
CarolineDenis wants to merge 20 commits into
mainfrom
issue-8101-fix-6266-after-review

Conversation

@CarolineDenis
Copy link
Copy Markdown
Contributor

@CarolineDenis CarolineDenis commented May 20, 2026

Fixes #8101

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

Test again #6308 (comment)

Summary by CodeRabbit

  • Chores

    • Improved uniqueness-rule detection and bulk-updating of matching rules.
    • Made migrations more robust and DB-alias aware; simplified tectonic/treedef and default creation flows.
    • Embedded and consolidated CO-children schema/localization updates in the migration.
    • Refined permission/role assignment and per-user admin-policy migration.
    • Made schema-config matching case-insensitive and corrected a relative-age field mapping.
    • Adjusted management command exception logging to include full stack traces.
  • Bug Fixes

    • Disabled verbose SQL query logging except when debug is enabled.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ebf278a-db0d-4bad-b256-a1b88f983877

📥 Commits

Reviewing files that changed from the base of the PR and between 2408d28 and 12d9efa.

📒 Files selected for processing (1)
  • specifyweb/specify/api/utils.py

📝 Walkthrough

Walkthrough

Refactors migration and uniqueness-rule behavior, makes patch migrations DB-alias aware, changes per-user permission migration and schema-config matching to be case-insensitive, inlines CO-children migration logic, and gates SQLAlchemy debug logging on settings.DEBUG.

Changes

Migration Logic & Uniqueness Rule Improvements

Layer / File(s) Summary
Uniqueness Rule Set-Based Matching
specifyweb/backend/businessrules/uniqueness_rules.py
create_uniqueness_rule and remove_uniqueness_rule now compare scope vs non-scope fieldPath sets for exact equality instead of relying on fieldPath__in filter counts; fix_global_default_rules precomputes global-rule signatures in Python and deletes matching discipline-scoped duplicates.
Catalog Number Rule Bulk Updates
specifyweb/backend/businessrules/migration_utils.py
catnum_rule_uneditable accumulates matching uniqueness-rule IDs and bulk-updates those rules' isDatabaseConstraint=True when matches exist; if none found, it creates the uniqueness rule as before.
Database Alias Awareness in Patch Migrations
specifyweb/backend/patches/migration_utils.py
update_is_accepted and update_coordinates derive the DB alias from schema_editor (default "default") and run updates via model _base_manager.using(db_alias).
Per-User Admin Policy Migration
specifyweb/backend/permissions/initialize.py
create_admins now checks for an existing per-user wildcard UserPolicy before creating admin policies; assign_users_to_roles rewrites exclusion SQL to a correlated NOT EXISTS subquery for (user, collection).
Tectonic Rank & Tree Definition Migrations
specifyweb/specify/migration_utils/default_cots.py, specifyweb/specify/migration_utils/tectonic_ranks.py
Simplify discipline selection and treedef creation with get_or_create; implement update-or-create for the "Root" treedef item; restrict revert deletions; pair and persist unlinked treedef/discipline lists.
Case-Insensitive Schema Config Field Matching
specifyweb/specify/migration_utils/update_schema_config.py
Change Splocalecontaineritem filters to name__iexact across 0023/0027/0029/0032/0034 flows; 0034 now updates description and name localized strings independently; fix COG-type container name casing used in revert.
Migration Command Logging & Import Refactoring
specifyweb/specify/management/commands/run_key_migration_functions.py, specifyweb/specify/migrations/0002_geo.py, specifyweb/specify/migrations/0008_ageCitations_fix.py, specifyweb/specify/migrations/0009_tectonic_ranks.py
Switch to logger.exception() to log stack traces in the migration command; re-source default-cots imports to migration_utils.default_cots; correct revert helper call ordering.
CO Children Schema Config Inlining
specifyweb/specify/migrations/0027_CO_children.py
Inline CO-children schema/localization update helpers into the migration file: create/update Splocalecontainer/Splocalecontaineritem/Splocaleitemstr rows with defaults, apply localized renames/descriptions, and provide symmetric revert behavior.
Conditional SQLAlchemy Query Logging
specifyweb/backend/stored_queries/execution.py, specifyweb/specify/api/utils.py
Wrap log_sqlalchemy_query(query) calls with if settings.DEBUG: and gate log_sqlalchemy_query itself on settings.DEBUG; compile SQL with MySQL dialect only when DEBUG is enabled.

Possibly related PRs

  • specify/specify7#8039: Overlaps changes to update_schema_config.py and schema-config matching/locale-string handling.
  • specify/specify7#6308: Related migration pipeline updates touching uniqueness-rule matching and migration command behavior.

Suggested reviewers

  • grantfitzsimmons
  • acwhite211
🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Automatic Tests ⚠️ Warning PR checklist marks "add automated tests" as UNCHECKED. No tests for migration utilities, tectonic ranks, update_schema_config, or patches changes that involve complex database operations. Add automated tests for modified utility functions and new migration functions handling database operations. Mark checklist item complete.
Testing Instructions ⚠️ Warning Testing instructions lack specificity: PR references external GitHub discussions without direct guidance; no step-by-step procedures or comprehensive component coverage documented. Add explicit, step-by-step testing instructions covering all affected components (migrations, business rules, permissions, schema config) with specific test scenarios for each modification.
Title check ❓ Inconclusive The title 'Issue 8101 fix 6266 after review' references the linked issue (#8101) and related PR (#6266) but lacks specificity about the actual changes made, which involve multiple bug fixes and code quality improvements across migration utilities, permissions, and logging. Consider a more descriptive title like 'Fix migration utilities, permissions, and logging per CodeRabbit review' to better convey the scope of changes for developers scanning history.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR addresses improvement suggestions from CodeRabbit's review of PR #6266, implementing fixes across migration utilities, permissions logic, and logging behavior as outlined in issue #8101.
Out of Scope Changes check ✅ Passed All changes align with the objective to implement CodeRabbit review suggestions: bug fixes in migration utilities (uniqueness rules, tree definitions), permissions (admin user migration, role assignment), logging (conditional DEBUG output), and schema config updates.

✏️ 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-8101-fix-6266-after-review

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
specifyweb/specify/migration_utils/update_schema_config.py (1)

1937-1950: 💤 Low value

Address the TODO comment and verify intended behavior.

The TODO at line 1940 questions whether unconditionally setting ishidden = True is correct. This forces all fields processed by this migration to be hidden, regardless of their current state or user preferences. If this is intentional, consider removing the TODO; otherwise, consider adding a condition or making the behavior configurable.

The change to allow partial updates (saving desc_str or name_str independently) is a good improvement over the previous all-or-nothing approach.

🤖 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/update_schema_config.py` around lines 1937
- 1950, The code currently sets item.ishidden = True for every matched item
unconditionally (in update_schema_config.py within the loop processing items and
Splocaleitemstr updates), which may be unintended; either remove the TODO and
leave this behavior if hiding every processed field is intentional, or change it
to a conditional/configurable action (e.g., respect an item's existing ishidden
value, check a migration flag/parameter, or only set ishidden when a specific
condition is met) and document the behavior; ensure Splocaleitemstr handling
(desc_str/text and name_str/text updates) remains unchanged so partial saves
still occur independently.
🤖 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/businessrules/uniqueness_rules.py`:
- Around line 459-471: The if-check and deletion are incorrectly placed outside
the loop so only the last UniquenessRule is evaluated; inside the for loop that
iterates
UniquenessRule.objects.filter(discipline_id=discipline_id).prefetch_related("uniquenessrulefield_set")
compute the signature (using rule.modelName, rule.isDatabaseConstraint and
frozenset(rule.uniquenessrulefield_set.values_list("fieldPath", "isScope"))) and
then immediately check if signature in global_rule_signatures, and if so call
rule.uniquenessrulefield_set.all().delete() and rule.delete() so each rule is
evaluated and removed as needed.

In `@specifyweb/specify/migration_utils/default_cots.py`:
- Around line 139-148: The code pairs two unordered querysets
(empty_tectonic_unit_treedefs and empty_disciplines) using zip which yields
nondeterministic matches; make the pairing deterministic by applying a stable
ordering (e.g., .order_by('id') or another immutable unique column) to both
QuerySets before converting to lists and zipping them (update the assignments
for empty_tectonic_unit_treedefs and empty_disciplines and any other similar
pairings around 154-160 so both sides use the same deterministic order).

In `@specifyweb/specify/migrations/0027_CO_children.py`:
- Around line 30-31: The migration defines camel_to_spaced_title_case which
calls re.sub but the regex module isn't imported; add an import for the re
module at the top of the file (e.g., import re) so camel_to_spaced_title_case
can call re.sub without raising NameError.
- Around line 69-77: The get_or_create call for Splocalecontaineritem is using
too many lookup fields causing duplicates; change
Splocalecontaineritem.objects.get_or_create(...) to use only the unique lookup
keys (name=field_name, container=sp_local_container) and move all other
attributes (type=java_type, ishidden=field_name.lower() in HIDDEN_FIELDS,
isrequired=field.required, issystem=table.system, version=0) into the defaults=
{...} argument so existing records are matched by name+container and new records
receive the other values via defaults.

---

Nitpick comments:
In `@specifyweb/specify/migration_utils/update_schema_config.py`:
- Around line 1937-1950: The code currently sets item.ishidden = True for every
matched item unconditionally (in update_schema_config.py within the loop
processing items and Splocaleitemstr updates), which may be unintended; either
remove the TODO and leave this behavior if hiding every processed field is
intentional, or change it to a conditional/configurable action (e.g., respect an
item's existing ishidden value, check a migration flag/parameter, or only set
ishidden when a specific condition is met) and document the behavior; ensure
Splocaleitemstr handling (desc_str/text and name_str/text updates) remains
unchanged so partial saves still occur independently.
🪄 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: 1c19e341-37fd-48e9-ab86-d9dd68ad79e7

📥 Commits

Reviewing files that changed from the base of the PR and between 5d85c03 and 7632dee.

📒 Files selected for processing (12)
  • specifyweb/backend/businessrules/migration_utils.py
  • specifyweb/backend/businessrules/uniqueness_rules.py
  • specifyweb/backend/patches/migration_utils.py
  • specifyweb/backend/permissions/initialize.py
  • specifyweb/specify/management/commands/run_key_migration_functions.py
  • specifyweb/specify/migration_utils/default_cots.py
  • specifyweb/specify/migration_utils/tectonic_ranks.py
  • specifyweb/specify/migration_utils/update_schema_config.py
  • specifyweb/specify/migrations/0002_geo.py
  • specifyweb/specify/migrations/0008_ageCitations_fix.py
  • specifyweb/specify/migrations/0009_tectonic_ranks.py
  • specifyweb/specify/migrations/0027_CO_children.py
💤 Files with no reviewable changes (1)
  • specifyweb/specify/migrations/0002_geo.py

Comment thread specifyweb/backend/businessrules/uniqueness_rules.py Outdated
Comment thread specifyweb/specify/migration_utils/default_cots.py
Comment thread specifyweb/specify/migrations/0027_CO_children.py
@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board May 20, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
specifyweb/specify/migration_utils/default_cots.py (1)

146-148: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the zip length mismatch explicit with strict=False.

Ruff (B905) is flagging this zip() for missing an explicit strict= argument. Since the two lists are intentionally different lengths (the leftover disciplines are handled in the loop at line 154), passing strict=False documents that intent and silences the lint.

Proposed tweak
-    for discipline, tectonic_unit_treedef in zip(
-        empty_disciplines, empty_tectonic_unit_treedefs
-    ):
+    for discipline, tectonic_unit_treedef in zip(
+        empty_disciplines, empty_tectonic_unit_treedefs, strict=False
+    ):
🤖 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/default_cots.py` around lines 146 - 148,
The zip call over empty_disciplines and empty_tectonic_unit_treedefs should be
made explicit about allowing length mismatch to satisfy Ruff B905; update the
loop that iterates "for discipline, tectonic_unit_treedef in
zip(empty_disciplines, empty_tectonic_unit_treedefs)" to pass strict=False so
the intentional unequal lengths are documented and the lint is silenced.
specifyweb/specify/migrations/0027_CO_children.py (1)

50-60: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use case-insensitive container matching throughout this migration.

These lookups are all exact lowercase matches. If a Splocalecontainer already exists with different casing, the forward path can create a duplicate container while the update/revert passes skip the existing rows. Please switch the lookup side to name__iexact and only normalize the name when creating a new row.

Also applies to: 121-123, 149-150

🤖 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/0027_CO_children.py` around lines 50 - 60,
Change the exact-lowercase lookups for Splocalecontainer to case-insensitive
matching: use name__iexact when calling get_or_create or filter (e.g., where
Splocalecontainer.objects.get_or_create(...) and
Splocalecontainer.objects.filter(...).first() are used) so existing containers
with different casing are matched; only normalize (lowercase) the name value
when actually creating a new Splocalecontainer record (i.e., pass the normalized
name into the create path), and apply the same change to the other similar
lookup sites in this migration (the other Splocalecontainer get_or_create/filter
usages mentioned).
🤖 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/migrations/0027_CO_children.py`:
- Around line 70-80: The reverse migration currently unconditionally deletes
Splocalecontaineritem rows created/reused by
update_table_field_schema_config_with_defaults; change the reverse logic so it
does not blindly delete matching items — instead, detect which rows were
actually created by this forward pass (preferably by checking the
get_or_create() returned-created case or by comparing the row's current
attributes to the exact defaults we set) and only delete those; for rows that
were reused (attributes differ or created flag absent), skip deletion and
restore their localized flags/values (ishidden/isrequired/issystem/version) in
place rather than removing the record; apply this change to the deletion block
around the Splocalecontaineritem removal and the later "unhide" loop (the code
that iterates over Splocalecontaineritem rows in
update_table_field_schema_config_with_defaults and the reverse path lines
~144-158).

---

Outside diff comments:
In `@specifyweb/specify/migration_utils/default_cots.py`:
- Around line 146-148: The zip call over empty_disciplines and
empty_tectonic_unit_treedefs should be made explicit about allowing length
mismatch to satisfy Ruff B905; update the loop that iterates "for discipline,
tectonic_unit_treedef in zip(empty_disciplines, empty_tectonic_unit_treedefs)"
to pass strict=False so the intentional unequal lengths are documented and the
lint is silenced.

In `@specifyweb/specify/migrations/0027_CO_children.py`:
- Around line 50-60: Change the exact-lowercase lookups for Splocalecontainer to
case-insensitive matching: use name__iexact when calling get_or_create or filter
(e.g., where Splocalecontainer.objects.get_or_create(...) and
Splocalecontainer.objects.filter(...).first() are used) so existing containers
with different casing are matched; only normalize (lowercase) the name value
when actually creating a new Splocalecontainer record (i.e., pass the normalized
name into the create path), and apply the same change to the other similar
lookup sites in this migration (the other Splocalecontainer get_or_create/filter
usages mentioned).
🪄 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: 76d67dfc-0cad-40cc-9ebb-25c5f9ae0735

📥 Commits

Reviewing files that changed from the base of the PR and between 7632dee and f847d7b.

📒 Files selected for processing (4)
  • specifyweb/backend/businessrules/uniqueness_rules.py
  • specifyweb/backend/stored_queries/execution.py
  • specifyweb/specify/migration_utils/default_cots.py
  • specifyweb/specify/migrations/0027_CO_children.py
✅ Files skipped from review due to trivial changes (1)
  • specifyweb/backend/stored_queries/execution.py

Comment on lines +70 to +80
sp_local_container_item, _ = Splocalecontaineritem.objects.get_or_create(
name=field_name,
container=sp_local_container,
defaults={
'type': java_type,
'ishidden': field_name.lower() in HIDDEN_FIELDS,
'isrequired': field.required,
'issystem': table.system,
'version': 0,
}
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't delete schema-config rows that the forward path may have reused.

update_table_field_schema_config_with_defaults() can reuse an existing Splocalecontaineritem, but the reverse path unconditionally deletes every matching item before the later "unhide" loop runs. That makes the second pass ineffective for these fields, and on drifted databases a reverse migration can remove preexisting schema-config rows instead of restoring their prior state. Please make the reverse path distinguish created rows from reused rows, or restore the original localized values/flags without deleting reused records.

Also applies to: 144-158

🤖 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/0027_CO_children.py` around lines 70 - 80, The
reverse migration currently unconditionally deletes Splocalecontaineritem rows
created/reused by update_table_field_schema_config_with_defaults; change the
reverse logic so it does not blindly delete matching items — instead, detect
which rows were actually created by this forward pass (preferably by checking
the get_or_create() returned-created case or by comparing the row's current
attributes to the exact defaults we set) and only delete those; for rows that
were reused (attributes differ or created flag absent), skip deletion and
restore their localized flags/values (ishidden/isrequired/issystem/version) in
place rather than removing the record; apply this change to the deletion block
around the Splocalecontaineritem removal and the later "unhide" loop (the code
that iterates over Splocalecontaineritem rows in
update_table_field_schema_config_with_defaults and the reverse path lines
~144-158).

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.

Address code improvement review for 6266

1 participant