From 64e471a5314c4c55e1cb9bd1c7e273e182e43f9e Mon Sep 17 00:00:00 2001 From: Andy Babic Date: Tue, 2 Jul 2024 20:59:21 +0100 Subject: [PATCH] Account for clashes that arise during download/update OR whilst uploading the file to storage and saving --- src/wagtail_bynder/views/mixins.py | 40 +++++++++++++++++------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/wagtail_bynder/views/mixins.py b/src/wagtail_bynder/views/mixins.py index 76c64c9..e7258b9 100644 --- a/src/wagtail_bynder/views/mixins.py +++ b/src/wagtail_bynder/views/mixins.py @@ -1,3 +1,5 @@ +import contextlib + from typing import TYPE_CHECKING from django.conf import settings @@ -13,7 +15,6 @@ class BynderAssetCopyMixin: - model = type[BynderAssetMixin] def create_object(self, asset_id: str) -> BynderAssetMixin: @@ -22,24 +23,29 @@ def create_object(self, asset_id: str) -> BynderAssetMixin: data = client.asset_bank_client.media_info(asset_id) obj.update_from_asset_data(data) try: - obj.save() - except IntegrityError as ie: + # If the asset finished saving in a different thread during the download/update process, + # return the pre-existing object + return self.model.objects.get(bynder_id=asset_id) + except self.model.DoesNotExist: try: - # If the 'bynder_id' is already taken, find the existing object to return - existing = self.model.objects.get(bynder_id=asset_id) - except self.model.DoesNotExist: - # The error must relate to a different field, so throw the original error - raise ie from None - else: - # If the new file was copied to media storage, ensure it is deleted + # Save the new object, triggering transfer of the file to media storage + obj.save() + except IntegrityError as integrity_error: + # It's likely the asset finished saving in a different thread while the file was + # being transferred to media storage try: - if obj.file.path != existing.file.path: - obj.file.delete() - except (ValueError, FileNotFoundError): - pass - return existing - else: - return obj + # Lookup the existing object + pre_existing = self.model.objects.get(bynder_id=asset_id) + except self.model.DoesNotExist: + # The IntegrityError must have been caused by a custom field, so reraise + raise integrity_error from None + else: + # If the newly-downloaded file was successfully copied to storage, delete it + with contextlib.suppress(ValueError, FileNotFoundError): + if obj.file.path != pre_existing.file.path: + obj.file.delete() + return pre_existing + return obj def update_object(self, asset_id: str, obj: BynderAssetMixin) -> BynderAssetMixin: client = get_bynder_client()