diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fd96336..7b88aac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to - ♿️(frontend) improve BoxButton a11y and native button semantics #2103 - ♿️(frontend) improve language picker accessibility #2069 - ♿️(frontend) add aria-hidden to decorative icons in dropdown menu #2093 +- 🐛(backend) move lock table closer to the insert operation targeted ### Fixed diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 2f03f5d6..2bb258f9 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -674,17 +674,8 @@ class DocumentViewSet( return drf.response.Response(serializer.data) - @transaction.atomic def perform_create(self, serializer): """Set the current user as creator and owner of the newly created object.""" - - # locks the table to ensure safe concurrent access - with connection.cursor() as cursor: - cursor.execute( - f'LOCK TABLE "{models.Document._meta.db_table}" ' # noqa: SLF001 - "IN SHARE ROW EXCLUSIVE MODE;" - ) - # Remove file from validated_data as it's not a model field # Process it if present uploaded_file = serializer.validated_data.pop("file", None) @@ -707,10 +698,18 @@ class DocumentViewSet( {"file": ["Could not convert file content"]} ) from err - obj = models.Document.add_root( - creator=self.request.user, - **serializer.validated_data, - ) + with transaction.atomic(): + # locks the table to ensure safe concurrent access + with connection.cursor() as cursor: + cursor.execute( + f'LOCK TABLE "{models.Document._meta.db_table}" ' # noqa: SLF001 + "IN SHARE ROW EXCLUSIVE MODE;" + ) + + obj = models.Document.add_root( + creator=self.request.user, + **serializer.validated_data, + ) serializer.instance = obj models.DocumentAccess.objects.create( document=obj, diff --git a/src/backend/core/models.py b/src/backend/core/models.py index d1869e13..c6ed9ae1 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -267,6 +267,16 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): if settings.USER_ONBOARDING_SANDBOX_DOCUMENT: # transaction.atomic is used in a context manager to avoid a transaction if # the settings USER_ONBOARDING_SANDBOX_DOCUMENT is unused + sandbox_id = settings.USER_ONBOARDING_SANDBOX_DOCUMENT + try: + template_document = Document.objects.get(id=sandbox_id) + except Document.DoesNotExist: + logger.warning( + "Onboarding sandbox document with id %s does not exist. Skipping.", + sandbox_id, + ) + return + with transaction.atomic(): # locks the table to ensure safe concurrent access with connection.cursor() as cursor: @@ -274,17 +284,6 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): f'LOCK TABLE "{Document._meta.db_table}" ' # noqa: SLF001 "IN SHARE ROW EXCLUSIVE MODE;" ) - - sandbox_id = settings.USER_ONBOARDING_SANDBOX_DOCUMENT - try: - template_document = Document.objects.get(id=sandbox_id) - except Document.DoesNotExist: - logger.warning( - "Onboarding sandbox document with id %s does not exist. Skipping.", - sandbox_id, - ) - return - sandbox_document = Document.add_root( title=template_document.title, content=template_document.content,