-
Notifications
You must be signed in to change notification settings - Fork 48
Issue 8124 8126 fix 7660 7682 2 6266 after review #8154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
melton-jason
wants to merge
57
commits into
v7_12_0_7_base
Choose a base branch
from
issue-8124-8126-fix-7660-7682-2-after-review-2
base: v7_12_0_7_base
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 49 commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
2a5eda8
fix:reverse_hide_component_fields to be set to Falses
CarolineDenis e8466af
fix: Do not dedupe localized strings across all languages
CarolineDenis 25bf8b2
fix: Simplify MultipleObjectsReturned
CarolineDenis 063a7c6
Fix: Use db_alias for migration db connexion
CarolineDenis 6cc5ffa
Fix: flips existing row back to isDatabaseConstraint=True
CarolineDenis 1ee0f1e
@CarolineDenis
CarolineDenis f0dd766
Fix: verify existing user permissions
CarolineDenis 6dfe4eb
Fix: Remove the use of f-string
CarolineDenis 8893687
fix: prevent incorrect reuse when pairing tree defs and disciplines
CarolineDenis 32e2972
fix: remove exclusion so partially migrated disciplines are repaired
CarolineDenis 6d1403c
Fix: Update splocalecontainer items
CarolineDenis d60939d
Fix: Remove shadowing import in geo migration
CarolineDenis 470d99d
Fix: Revert relative age migration instead of applying again
CarolineDenis befb73b
Fix fix order of revert migration in tectonic migration
CarolineDenis 8a1e35b
Fix: Indentation
CarolineDenis 82bb520
Fix: Use deterministic ordering before positional pairing
CarolineDenis 8edb112
Fix: Log only on debug
CarolineDenis efc8228
Fix: Fix age type
CarolineDenis 2366664
Fix: Improve logger
CarolineDenis 4c0f9f8
Fix: Remove unecessary param in def migration
CarolineDenis fac87e9
Fix: Remove projects from legacy tests
CarolineDenis c8663e9
Fix: Change settings import
CarolineDenis a940612
Fix:Incomplete field initialization in conditional createt
CarolineDenis 4e1d45a
Fix: Incomplete field initialization in conditional create
CarolineDenis e228051
Fix: Put model name in lower case for consistency and reversability
CarolineDenis aff97b8
Fix: Revert chnages
CarolineDenis 1e13637
Fix: Remove double id rank
CarolineDenis 624deee
Fix: Indentation
CarolineDenis cd740a7
Fix: Add strict=False to allow diff length
CarolineDenis 05f6298
Fix: Remove duplicate
CarolineDenis cf79196
Fix: Remove using from geo migration
CarolineDenis c341f55
Fix: Bring back legacy project test
CarolineDenis 72414b6
Fix: Remove values_list() in favor of .all
CarolineDenis 7112c84
fix: stop shadowing catnum unique in collection function for migration
melton-jason eda65ab
refactor: improve readability of condition when enabling editable uni…
melton-jason 6e37e1e
fix: simplify fix_global_rules helper function
melton-jason 48face1
fix: use manager over base manager for tree patches
melton-jason fdfe21b
fix: remove check for old newly created DBs in specify 7
melton-jason 96bf0cc
fix: bring back is_sp6_user_permissions_migrated
melton-jason cfad41f
fix: change ordering of admin checks to evaluate more common first
melton-jason 2e1835e
fix: remove unused query
melton-jason f4d6491
refactor: use defaults in get_or_create for Roles
melton-jason f792d5e
chore: add refactor note
melton-jason 91aaf38
refactor: move DEBUG check into log_sqlalchemy_query
melton-jason 6b4d796
fix: remove unused log_sqlalchemy_query function
melton-jason ab8320b
fix: incorrect Discipline -> tectonicunittreedef pairing when resolvi…
melton-jason 4196c1e
refactor: optimize fix_taxon_treedef_discipline_links to one query
melton-jason 6e1902e
refactor: move fix_tectonic_links to TectonicUnit file
melton-jason 39ec012
fix: forward tectonic unit migration functions
melton-jason 9f9df79
fix: use Migrator user for all run_key_migration operations
melton-jason a95bc71
perf: reduce set_discipline_for_taxon_treedefs to single DB hit
melton-jason 1136e52
refactor: collapse default tectonicunit ranks to tuple
melton-jason 5418c64
fix: simplify and move reverse tectonic root node migration
melton-jason 419d69b
fix: handle case when businessrule app is not ready but migrations ar…
melton-jason 19f5c1a
refactor: move reverse migration to migration file
melton-jason 9ddf13e
fix: account for custom Tectonic Trees when reverting migration
melton-jason 3396fe4
fix: handle case when root node is synonymized in reverse migration
melton-jason File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,71 +1,31 @@ | ||
| from typing import Tuple, List | ||
|
|
||
| from specifyweb.backend.businessrules.uniqueness_rules import create_uniqueness_rule | ||
|
|
||
|
|
||
| def catnum_rule_editable(apps, schema_editor=None): | ||
| """ Find any CollectionObject catalogNumber must be unique to Collection | ||
| rules which are readonly on the frontend (have isDatabaseConstraint=True) | ||
| and set their isDatabaseConstraint=False. | ||
|
|
||
| Generally should be run only after migration businessrules/0003 has been | ||
| applied | ||
| """ | ||
| UniquenessRule = apps.get_model("businessrules", "UniquenessRule") | ||
|
|
||
| model_rules = UniquenessRule.objects.filter(modelName="Collectionobject", isDatabaseConstraint=True) | ||
|
|
||
| catalog_number_rules: List[int] = [] | ||
| for rule in model_rules: | ||
| model_rules = UniquenessRule.objects.filter( | ||
| modelName="Collectionobject", | ||
| isDatabaseConstraint=True | ||
| ) | ||
|
|
||
| catalog_number_rules: list[int] = [] | ||
| for rule in model_rules: | ||
| rule_fields = rule.uniquenessrulefield_set.all() | ||
|
|
||
| fields = rule_fields.filter(isScope=False) | ||
| scopes = rule_fields.filter(isScope=True) | ||
|
|
||
| # We're only interested in the rule "CollectionObject catalogNumber | ||
| # We're only interested in the rule "CollectionObject catalogNumber | ||
| # must be unique to Collection" | ||
| # We check for length of fields and scopes because get() raises an | ||
| # We check for length of fields and scopes because get() raises an | ||
| # exception if more than one result is returned | ||
| if (len(fields) == 1 and len(scopes) == 1) and (fields.get().fieldPath.lower() == "catalognumber" and scopes.get().fieldPath.lower() == "collection"): | ||
| catalog_number_rules.append(rule.id) | ||
|
|
||
| rules_to_update = UniquenessRule.objects.filter(id__in=catalog_number_rules) | ||
| rules_to_update.update(isDatabaseConstraint=False) | ||
|
|
||
|
|
||
| def catnum_rule_uneditable(apps, schema_editor=None): | ||
| """ Find any CollectionObject catalogNumber must be unique to Collection | ||
| rules which are editable on the frontend (have isDatabaseConstraint=False) | ||
| and set their isDatabaseConstraint=True. | ||
|
|
||
| Generally should be run when migration businessrules/0003 is being reverted | ||
| """ | ||
| Discipline = apps.get_model("specify", "Discipline") | ||
| UniquenessRule = apps.get_model("businessrules", "UniquenessRule") | ||
|
|
||
| for discipline in Discipline.objects.all(): | ||
| model_rules = UniquenessRule.objects.filter(modelName="Collectionobject", discipline_id=discipline.id, isDatabaseConstraint=False) | ||
|
|
||
| has_catalognumber_rule = False | ||
| for rule in model_rules: | ||
| rule_fields = rule.uniquenessrulefield_set.all() | ||
|
|
||
| fields = rule_fields.filter(isScope=False) | ||
| scopes = rule_fields.filter(isScope=True) | ||
|
|
||
| # We're only interested in the rule "CollectionObject catalogNumber | ||
| # must be unique to Collection" | ||
| # We check for length of fields and scopes because get() raises an | ||
| # exception if more than one result is returned | ||
| if (len(fields) == 1 and len(scopes) == 1) and (fields.get().fieldPath.lower() == "catalognumber" and scopes.get().fieldPath.lower() == "collection"): | ||
| has_catalognumber_rule = True | ||
|
|
||
| if not has_catalognumber_rule: | ||
| create_uniqueness_rule( | ||
| model_name="Collectionobject", | ||
| discipline=discipline, | ||
| is_database_constraint=True, | ||
| fields=["catalogNumber"], | ||
| scopes=["collection"], | ||
| registry=apps, | ||
| ) |
66 changes: 41 additions & 25 deletions
66
specifyweb/backend/businessrules/migrations/0004_catnum_uniquerule.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,48 +1,64 @@ | ||
| from django.db import migrations | ||
|
|
||
| from specifyweb.backend.businessrules.migration_utils import catnum_rule_editable, catnum_rule_uneditable | ||
| from specifyweb.backend.businessrules.migration_utils import catnum_rule_editable | ||
| from specifyweb.backend.businessrules.uniqueness_rules import create_uniqueness_rule | ||
|
|
||
|
|
||
| def catnum_rule_editable(apps, schema_editor): | ||
| UniquenessRule = apps.get_model('businessrules', 'UniquenessRule') | ||
| UniquenessRuleField = apps.get_model('businessrules', 'UniquenessRuleField') | ||
|
|
||
| candidate_rules_with_field: tuple[int] = tuple(UniquenessRuleField.objects.filter(uniquenessrule__modelName__iexact='collectionobject', uniquenessrule__isDatabaseConstraint=True, fieldPath__iexact='catalognumber', isScope=False).values_list('uniquenessrule_id', flat=True)) | ||
| def catnum_rule_uneditable(apps, schema_editor): | ||
| """ Find any CollectionObject catalogNumber must be unique to Collection | ||
| rules which are editable on the frontend (have isDatabaseConstraint=False) | ||
| and set their isDatabaseConstraint=True. | ||
|
|
||
| candidate_rules_with_scope: tuple[int] = tuple(UniquenessRuleField.objects.filter(uniquenessrule_id__in=candidate_rules_with_field, fieldPath__iexact='collection', isScope=True).values_list('uniquenessrule_id', flat=True)) | ||
| Generally should be run when migration businessrules/0003 is being reverted | ||
| """ | ||
| Discipline = apps.get_model("specify", "Discipline") | ||
| UniquenessRule = apps.get_model("businessrules", "UniquenessRule") | ||
|
|
||
| candidate_rules = UniquenessRule.objects.filter(id__in=candidate_rules_with_scope) | ||
| candidate_rules.update(isDatabaseConstraint=False) | ||
| for discipline in Discipline.objects.all(): | ||
| # REFACTOR: Some of these queries should be able to be combined to | ||
| # improve performance and limit how often we need to hit the database | ||
| model_rules = UniquenessRule.objects.filter( | ||
| modelName="Collectionobject", | ||
| discipline_id=discipline.id, | ||
| isDatabaseConstraint=False | ||
| ) | ||
|
|
||
| def catnum_rule_uneditable(apps, schema_editor): | ||
| Discipline = apps.get_model('specify', 'Discipline') | ||
| UniquenessRule = apps.get_model('businessrules', 'UniquenessRule') | ||
| UniquenessRuleField = apps.get_model('businessrules', 'UniquenessRuleField') | ||
| has_catalognumber_rule = False | ||
| matching_rule_ids: list[int] = [] | ||
| for rule in model_rules: | ||
| rule_fields = rule.uniquenessrulefield_set.all() | ||
|
|
||
| for discipline in Discipline.objects.all(): | ||
| candidate_rules_with_field: tuple[int] = tuple(UniquenessRuleField.objects.filter(uniquenessrule__modelName__iexact='collectionobject', uniquenessrule__discipline=discipline.id, uniquenessrule__isDatabaseConstraint=False, fieldPath__iexact='catalognumber', isScope=False).values_list('uniquenessrule_id', flat=True)) | ||
| fields = rule_fields.filter(isScope=False) | ||
| scopes = rule_fields.filter(isScope=True) | ||
|
|
||
| candidate_rules_with_scope: tuple[int] = tuple(UniquenessRuleField.objects.filter(uniquenessrule_id__in=candidate_rules_with_field, fieldPath__iexact='collection', isScope=True).values_list('uniquenessrule_id', flat=True)) | ||
| # We're only interested in the rule "CollectionObject catalogNumber | ||
| # must be unique to Collection" | ||
| # We check for length of fields and scopes because get() raises an | ||
| # exception if more than one result is returned | ||
| if (len(fields) == 1 and len(scopes) == 1) and (fields.get().fieldPath.lower() == "catalognumber" and scopes.get().fieldPath.lower() == "collection"): | ||
| has_catalognumber_rule = True | ||
| matching_rule_ids.append(rule.id) | ||
|
|
||
| candidate_rules = UniquenessRule.objects.filter(id__in=candidate_rules_with_scope) | ||
| if len(candidate_rules) == 0: | ||
| if has_catalognumber_rule: | ||
| UniquenessRule.objects.filter( | ||
| id__in=matching_rule_ids).update(isDatabaseConstraint=True) | ||
| else: | ||
| create_uniqueness_rule( | ||
| model_name='Collectionobject', | ||
| model_name="Collectionobject", | ||
| discipline=discipline, | ||
| is_database_constraint=True, | ||
| fields=['catalogNumber'], | ||
| scopes=['collection'], | ||
| registry=apps | ||
| fields=["catalogNumber"], | ||
| scopes=["collection"], | ||
| registry=apps, | ||
| ) | ||
| else: | ||
| candidate_rules.update(isDatabaseConstraint=True) | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ('businessrules', '0003_catnum_constraint') | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RunPython(catnum_rule_editable, catnum_rule_uneditable, atomic=True) | ||
| migrations.RunPython(catnum_rule_editable, | ||
| catnum_rule_uneditable, atomic=True) | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(note for discussion)
I left this comment in the code, but I'll leave this as a comment on the PR for visibility.
There was a noticeable change in the asymptotic growth (time complexity) with this change.
Noticeably, the previous implementation of
fix_global_default_rules:Importantly, the number of Queries and iteration only grew on the number of Disciplines. So assuming the number of Disciplines remained the same, there could be any number of added Uniqueness Rules within those Disciplines without seeing a slowdown or additional database queries.
(This is only partly/theoretically true: there will be a slowdown for those queries if there is a sufficiently large amount of Uniqueness Rules and the queries are performed on unindexed fields or in a way that can't utilize the indexes, though I'm holding that slowdown as negligible or constant between the two approaches).
I've cleaned up the new approach and eliminated extra queries in
6e37e1e(this PR), but there still is a change in the approach:Our "bottleneck" is now largely on the amount of scoped Uniqueness Rules, rather than the number of Disciplines.
Practically, I'm not sure how much of a slowdown this would introduce.
If we have the time, we can try and resolve this slowdown (I imagine we can use and filter on annotations on the query for scoped rules for duplicate rules, or just revert back to something like the past approach if there are no bugs), and/or we can evaluate how much of a slowdown this introduces.