Skip to content

feat!: upgrade code and fix get_storage_class ( compatibility django42 and django52) #36628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

awais786
Copy link
Contributor

@awais786 awais786 commented Apr 29, 2025

openedx/public-engineering#340

Fixing breaking change for django52.

  • Verify the image functionality on sandbox.

Replaced usage of `get_storage_class`, which was deprecated in Django 4.2 and removed in 5.1,
with `import_string` to support dynamic storage class loading.
@awais786 awais786 changed the title feat!: upgrade codebase for compatibility with Django 4.2 and 5.2 feat!: upgrade code and fix get_storage_class ( compatibility django42 and django52) Apr 29, 2025
Comment on lines 39 to 40
storage_class_path = config.get('class')
storage_class = import_string(storage_class_path)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure if this case could ever occur but if the contents of storage_class_path are incorrect, there could be an error which we should catch e.g

try: storage_class = import_string(storage_class_path) except ImportError as e: raise ImproperlyConfigured( f"Could not import storage class '{storage_class_path}': {e}" )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@musanaeem
Copy link

LGTM!

Comment on lines 39 to 41
storage_class_path = config.get('class')
storage_class = import_string(storage_class_path)
return storage_class(**config.get('options', {}))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are not we using the below one?

from django.core.files.storage import storages
storage_class = storages(config.get('class'))
return storage_class(**config.get('options', {}))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@awais786 awais786 force-pushed the fixing-django-storages-dj52 branch from 2ffa43e to 03e8c83 Compare May 6, 2025 11:17
@awais786
Copy link
Contributor Author

@feanil Please review

@awais786 awais786 marked this pull request as ready for review May 13, 2025 16:20
@awais786 awais786 requested a review from a team as a code owner May 13, 2025 16:20
Comment on lines 39 to 44
if not storage_class_path:
storage_class_path = (
getattr(settings, 'DEFAULT_FILE_STORAGE', None) or
getattr(settings, 'STORAGES', {}).get('default', {}).get('BACKEND') or
'django.core.files.storage.FileSystemStorage'
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we fallback to the default storage class, I think we should also have a named storage class in the STORAGES dictionary. Maybe named profile_images?

This way, operators could migrate to defining their various storages config via the new STORAGES config and we can eventually deprecate the old configuration settings.

Copy link
Contributor Author

@awais786 awais786 May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@feanil you mean some thing like this

storage_class_path = (
    getattr(settings, 'DEFAULT_FILE_STORAGE', None)  # legacy setting
    or getattr(settings, 'STORAGES', {}).get('profile_images', {}).get('BACKEND')  # named storage
    or getattr(settings, 'STORAGES', {}).get('default', {}).get('BACKEND')  # default
    or 'django.core.files.storage.FileSystemStorage'  # final fallback
)

Along some settings in common.py

STORAGES = {
    'profile_images': {
        'BACKEND': 'storages.backends.s3boto3.S3Boto3Storage',
        'OPTIONS': {
            'bucket_name': 'my-bucket',
            ...
        }
    },
    'default': {
        'BACKEND': 'django.core.files.storage.FileSystemStorage'
    }
}

@awais786
Copy link
Contributor Author

awais786 commented May 15, 2025

@feanil I have verified on django42 this solution will not work. These settings cannot declare together.

STORAGES = {
    "profile_images": {
        "BACKEND": "openedx.core.storage.OverwriteStorage",
        "OPTIONS": {
            "location": "profile-images/",
            "base_url": "profile-images/",
        },
    },
    'default': {
        'BACKEND': 'django.core.files.storage.FileSystemStorage'
    }
}

DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'

These cannot declared together. It raises error

django.core.exceptions.ImproperlyConfigured: DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive.

Django code reference

Right now DEFAULT_FILE_STORAGE is declared at multiple places and this change will increase the scope of this PR. We can add followup ticket for cleaning purpose. openedx/public-engineering#369


if not storage_class_path:
storage_class_path = (
getattr(settings, 'STORAGES', {}).get('profile_images', {}).get('BACKEND') or # named storages
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profile_images it does not exists right now so it will use following storages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants