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 23 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.

@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:
storages_config = getattr(settings, 'STORAGES', {})

if 'PROFILE_IMAGE_STORAGE' in storages_config:
Copy link
Contributor Author

@awais786 awais786 May 16, 2025

Choose a reason for hiding this comment

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

Note: This implementation is forward-compatible and is intended to work when the legacy PROFILE_IMAGE_BACKEND setting is removed and the new Django STORAGES configuration is used instead. For example:

STORAGES = {
    'PROFILE_IMAGE_STORAGE': {
        'BACKEND': 'storages.backends.s3boto3.S3Boto3Storage',
        'OPTIONS': {
            'bucket_name': 'profiles',
            'default_acl': 'public',
            'location': 'profile/images',
        }
    }
}

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