Skip to content

Commit b0ca6be

Browse files
authored
Merge pull request #42 from octue/destroy-ganon-rescue-princess
Destroy Ganon, Rescue Princess
2 parents 67e6324 + 5e49cf4 commit b0ca6be

File tree

3 files changed

+43
-9
lines changed

3 files changed

+43
-9
lines changed

django_gcp/storage/fields.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,8 @@ def on_commit_valid():
279279
f"Unable to determine field state for {self._get_fieldname(model_instance)}. The most likely cause of this doing an operation (like migration) without setting GCP_STORAGE_OVERRIDE_BLOBFIELD_VALUE=True. Otherwise, please contact the django_gcp developers and describe what you're doing along with this exception stacktrace. Value was: {json.dumps(value)}"
280280
)
281281

282-
# Pop cached DB values so you can re-use an instantiated form after saving it
283-
self._existing_value = {"_cached": new_value}
282+
# Cache DB values in the instance so you can reuse it without multiple DB queries
283+
model_instance._state.fields_cache[self.attname] = new_value
284284

285285
return new_value
286286

@@ -478,14 +478,14 @@ def _get_existing_path(self, instance):
478478
"""Gets the existing (saved in db) path; will raise an error if the instance is unsaved.
479479
Caches value on the instance to avoid duplicate database calls within a transaction.
480480
"""
481-
if self._existing_value is None:
482-
# pylint: disable-next=protected-access
481+
# pylint: disable=protected-access
482+
if self.attname not in instance._state.fields_cache.keys():
483483
pk_name = instance._meta.pk.name
484484
# pylint: disable-next=protected-access
485485
existing = instance.__class__._default_manager.get(**{pk_name: getattr(instance, pk_name)})
486486
# Cache value in a dict because None is a valid value
487-
self._existing_value = {"_cached": getattr(existing, self.attname)}
488-
value_or_dict = self._existing_value["_cached"] or {}
487+
instance._state.fields_cache[self.attname] = getattr(existing, self.attname)
488+
value_or_dict = instance._state.fields_cache.get(self.attname, None) or {}
489489
return value_or_dict.get("path", None)
490490

491491
def _get_fieldname(self, instance):

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-gcp"
3-
version = "0.10.1"
3+
version = "0.10.2"
44
description = "Utilities to run Django on Google Cloud Platform"
55
authors = ["Tom Clark"]
66
license = "MIT"

tests/test_storage_fields.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ def test_update_valid_unchanged(self):
314314

315315

316316
class TestCallableBlobField(BlobModelFactoryMixin, StorageOperationsMixin, TestCase):
317-
"""Inherits from transaction test case, because we use an on_commit
318-
hook to move ingressed files once a database save has been made.
317+
"""Test the callbacks functionality.
318+
Use normal test case, allowing on_commit hook to execute.
319319
"""
320320

321321
def test_on_change_callback_execution(self):
@@ -366,3 +366,37 @@ def test_on_change_callback_execution(self):
366366
)
367367
instance = form.save()
368368
self.assertEqual(mock_on_commit.call_count, 4)
369+
370+
371+
class TestModelBlobField(BlobModelFactoryMixin, StorageOperationsMixin, TestCase):
372+
"""Extra tests for corner cases and model behaviour"""
373+
374+
def test_no_corruption_with_multiple_models(self):
375+
"""Tests for the presence of a nasty corner case where
376+
multiple models loaded into memory at once would conflict in
377+
their behaviour when determining the existing_path
378+
"""
379+
380+
blob_name_1 = self._prefix_blob_name("no_corruption_1.txt")
381+
blob_name_2 = self._prefix_blob_name("no_corruption_2.txt")
382+
# self._create_test_blob(self.bucket, blob_name_1, "")
383+
# self._create_test_blob(self.bucket, blob_name_2, "")
384+
385+
# Create two instances
386+
with override_settings(GCP_STORAGE_OVERRIDE_BLOBFIELD_VALUE=True):
387+
obj_1 = ExampleBlobFieldModel.objects.create(blob={"path": blob_name_1})
388+
obj_1.save()
389+
# In the captured behaviour, the cache of the second instance overwrite the first
390+
obj_2 = ExampleBlobFieldModel.objects.create(blob={"path": blob_name_2})
391+
obj_2.save()
392+
393+
# Edit the first, without changing the KMZ field (and outside the settings override)
394+
new_obj_1 = ExampleBlobFieldModel.objects.get(id=obj_1.id)
395+
new_obj_1.category = "test-change"
396+
397+
# Note this is not a blankable model, and the observed flaw in flushing
398+
# the cache in this case manifests in passing a None value instead of
399+
# the correct existing path being passed through. This save() thus
400+
# resulted in an IntegrityError.
401+
new_obj_1.save()
402+
self.assertEqual(new_obj_1.blob["path"], blob_name_1)

0 commit comments

Comments
 (0)