diff --git a/specifyweb/specify/autonumbering.py b/specifyweb/specify/autonumbering.py index 403c7d5159f..a68c3bcc8d7 100644 --- a/specifyweb/specify/autonumbering.py +++ b/specifyweb/specify/autonumbering.py @@ -11,6 +11,7 @@ from specifyweb.specify.scoping import Scoping from specifyweb.specify.datamodel import datamodel +from specifyweb.specify.tree_extras import is_treetable logger = logging.getLogger(__name__) @@ -43,22 +44,29 @@ def do_autonumbering(collection, obj, fields: list[tuple[UIFormatter, Sequence[s for formatter, vals in fields ] - with lock_tables(*get_tables_to_lock(collection, obj, [formatter.field_name for formatter, _ in fields])): + writing_to = set([obj._meta.db_table]) + reading_from = get_tables_to_read_lock(collection, obj, [formatter.field_name for formatter, _ in fields]) + with lock_tables(read_tables=reading_from, write_tables=writing_to): for apply_autonumbering_to in thunks: apply_autonumbering_to(obj) obj.save() -def get_tables_to_lock(collection, obj, field_names) -> set[str]: - # TODO: Include the fix for https://github.com/specify/specify7/issues/4148 - from specifyweb.businessrules.models import UniquenessRule - +def get_tables_to_read_lock(collection, obj, field_names: list[str]) -> set[str]: obj_table = obj._meta.db_table scope_table = Scoping(obj).get_scope_model() - tables = {obj._meta.db_table, 'django_migrations', UniquenessRule._meta.db_table, 'discipline', - scope_table._meta.db_table} + tables = set([scope_table._meta.db_table, *get_uniqueness_rule_tables(collection, obj_table, field_names)]) + + if is_treetable(obj): + tables.update(get_tree_tables_to_lock(obj_table)) + + return tables + +def get_uniqueness_rule_tables(collection, obj_table: str, field_names: list[str]) -> set[str]: + from specifyweb.businessrules.models import UniquenessRule, UniquenessRuleField + tables = set(['django_migrations', UniquenessRule._meta.db_table, UniquenessRuleField._meta.db_table, 'discipline']) rules = UniquenessRule.objects.filter( modelName=obj_table, discipline=collection.discipline) @@ -86,3 +94,6 @@ def get_tables_from_field_path(model: str, field_path: str) -> list[str]: table = datamodel.get_table_strict(other_model) return tables + +def get_tree_tables_to_lock(tree_table: str) -> set[str]: + return set(['discipline', f'{tree_table}def', f'{tree_table}defitem']) \ No newline at end of file diff --git a/specifyweb/specify/lock_tables.py b/specifyweb/specify/lock_tables.py index e6c2e75fbce..264c9dc6f03 100644 --- a/specifyweb/specify/lock_tables.py +++ b/specifyweb/specify/lock_tables.py @@ -1,20 +1,29 @@ -from django.db import connection -from contextlib import contextmanager import logging +from typing import Set +from contextlib import contextmanager + +from django.db import connection + logger = logging.getLogger(__name__) @contextmanager -def lock_tables(*tables): +def lock_tables(read_tables: Set[str], write_tables: Set[str]): cursor = connection.cursor() if cursor.db.vendor != 'mysql': logger.warning("unable to lock tables") yield else: try: - cursor.execute('lock tables %s' % - ' write, '.join(tables) + ' write') + # If a table is present in in both read and write arguments, + # remove the table from the read set + final_read_tables = read_tables.difference(write_tables) + write_statement = ','.join( + [table + ' write' for table in write_tables]) + ',' if len(write_tables) > 0 else '' + read_statement = ','.join( + [table + ' read' for table in final_read_tables]) + cursor.execute(f'lock tables {write_statement}{read_statement};') yield finally: cursor.execute('unlock tables') diff --git a/specifyweb/specify/tree_extras.py b/specifyweb/specify/tree_extras.py index c672d6bca1a..5dba4df97a0 100644 --- a/specifyweb/specify/tree_extras.py +++ b/specifyweb/specify/tree_extras.py @@ -11,11 +11,9 @@ from django.db import models, connection -from django.db.models import F, Q, ProtectedError -from django.conf import settings +from django.db.models import F, ProtectedError from specifyweb.businessrules.exceptions import TreeBusinessRuleException -import specifyweb.specify.models as spmodels from .auditcodes import TREE_BULK_MOVE, TREE_MERGE, TREE_SYNONYMIZE, TREE_DESYNONYMIZE @@ -731,11 +729,7 @@ def renumber_tree(table): Sptasksemaphore.objects.filter(taskname__in=tasknames).update(islocked=False) def is_treedefitem(obj): - return issubclass(obj.__class__, TreeRank) or bool( - re.search(r"treedefitem'>$", str(obj.__class__), re.IGNORECASE) - ) + return issubclass(obj.__class__, TreeRank) or obj._meta.db_table.lower().endswith("treedefitem") -def is_treedef(obj): - return issubclass(obj.__class__, Tree) or bool( - re.search(r"treedef'>$", str(obj.__class__), re.IGNORECASE) - ) +def is_treetable(obj): + return issubclass(obj.__class__, Tree) or hasattr(obj, "definitionitem") \ No newline at end of file