Opened 34 hours ago

Last modified 33 hours ago

#36269 new Cleanup/optimization

Document overriding STORAGES for tests when using callable storage in a FileField

Reported by: emillumine Owned by:
Component: File uploads/storage Version: 5.1
Severity: Normal Keywords: override_settings, storages
Cc: emillumine Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

It seems that the default storage and other user defined storages dont have the same behaviour in tests.
When overriding the settings.STORAGES in tests we can easily replace the default storage whereas it doesn't work for another user defined storage.
I tried to reproduce my problem with a minimal code below.

It may seems successful at first because the first assertion assert storages["other_storage"]._location == "other_location" is green. But the last one assert test.other_file.storage._location == "other_location" is not. Its value is the one defined in settings.STORAGES.

I would expect a similar behaviour in both cases.

# settings.py
STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    },
    "staticfiles": {
        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
    },
    "other_storage": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    }
}
# test.py

from django.test import override_settings
from django.core.files.storage import default_storage
from django.core.files.storage import storages
from django.core.files.storage import InMemoryStorage
from django.db import models
from django.core.files.base import ContentFile


def select_other_storage():
    return storages["other_storage"]

def select_default_storage():
    return default_storage


class TestModel(models.Model):
    other_file = models.FileField(storage=select_other_storage)
    default_file = models.FileField(storage=select_default_storage)

    class Meta:
        app_label = "test"

@override_settings(
    STORAGES={
        "default": {
            "BACKEND": "django.core.files.storage.InMemoryStorage",
            "OPTIONS": {
                "location": "default_location",
                },
            },
        "other_storage": {
            "BACKEND": "django.core.files.storage.InMemoryStorage",
            "OPTIONS": {
                "location": "other_location",
                },
        }
    }
)
def test_override_settings():
    assert default_storage._location == "default_location"
    assert storages["other_storage"]._location == "other_location"

    test = TestModel(
        other_file=ContentFile("test data", name="test.pdf"),
        default_file=ContentFile("test data", name="test.pdf"),
    )
    assert isinstance(test.default_file.storage, InMemoryStorage)
    assert test.default_file.storage._location == "default_location"
    # following assertions fail
    assert isinstance(test.other_file.storage, InMemoryStorage)
    assert test.other_file.storage._location == "other_location"

According to the ticket's flags, the next step(s) to move this issue forward are:

  • To provide a patch by sending a pull request. Claim the ticket when you start working so that someone else doesn't duplicate effort. Before sending a pull request, review your work against the patch review checklist. Check the "Has patch" flag on the ticket after sending a pull request and include a link to the pull request in the ticket comment when making that update. The usual format is: [https://github.com/django/django/pull/#### PR].

Change History (2)

comment:1 by emillumine, 34 hours ago

Version: 5.25.1

comment:2 by Sarah Boyce, 33 hours ago

Summary: Cant override storage used in a FileField other than the default storageDocument overriding STORAGES for tests when using callable storage in a FileField
Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

To me, this is expected based off the documentation:

https://docs.djangoproject.com/en/5.1/topics/files/#using-a-callable

Your callable will be evaluated when your models classes are loaded...

However, I think it might be nice to update the docs with an example similar to the default storage in order to make this easier to test.

We would need to check if this works but maybe something like:

from django.core.files.storage import storages
from django.db import models
from django.utils.functional import LazyObject


class MyStorage(LazyObject):
    def _setup(self):
        self._wrapped = storages["mystorage"]


select_storage = MyStorage()

class MyModel(models.Model):
    upload = models.FileField(storage=select_storage)

With a comment around testing and why you might want to do this

Note: See TracTickets for help on using tickets.
Back to Top