Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/check-reserved-keywords.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.11

Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python-version: ['3.11']
toxenv: [django42, quality]
python-version: ['3.11', '3.12']
toxenv: [django42, django52, quality]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -37,7 +37,7 @@ jobs:

- name: Run Coverage
if: matrix.python-version == '3.11' && matrix.toxenv=='django42'
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
flags: unittests
fail_ci_if_error: true
Expand All @@ -51,10 +51,10 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.11

Expand All @@ -63,7 +63,7 @@ jobs:

- name: Install Dependencies
run: |
pip install "Django>=4.2,<4.3"
pip install "Django>=4.2,<5.0"
pip install -r requirements/ci.txt
pip install -r requirements/test.txt

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pypi-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.11

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/verify_changed_contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.11

Expand Down
7 changes: 1 addition & 6 deletions edxval/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
}

# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.4/ref/settings/#allowed-hosts
# See https://docs.djangoproject.com/en/5.2/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []

# Local time zone for this installation. Choices can be found here:
Expand All @@ -42,10 +42,6 @@
# to load the internationalization machinery.
USE_I18N = True

# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True

# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True

Expand Down Expand Up @@ -179,7 +175,6 @@
DIRECTORY_PREFIX='video-transcripts/',
)


# Required by Django 2.2 to run management commands.
TEMPLATES = [
{
Expand Down
99 changes: 99 additions & 0 deletions edxval/tests/test_storages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@

"""
Unit tests for django-storages
"""

from unittest import TestCase

from django.conf import settings
from django.test.utils import override_settings

from edxval.utils import get_video_image_storage, get_video_transcript_storage
from storages.backends.s3boto3 import S3Boto3Storage # pylint: disable=wrong-import-order


class S3Boto3TestCase(TestCase):
"""Unit tests for verifying the S3Boto3 storage backend selection logic"""

def setUp(self):
self.storage = S3Boto3Storage()

def test_video_image_backend(self):
# settings file contains the `VIDEO_IMAGE_SETTINGS` but dont'have STORAGE_CLASS
# so it returns the default storage.

storage = get_video_image_storage()
storage_class = storage.__class__

self.assertEqual(
'django.core.files.storage.filesystem.FileSystemStorage',
f"{storage_class.__module__}.{storage_class.__name__}",
)

@override_settings(VIDEO_IMAGE_SETTINGS={
'STORAGE_CLASS': 'storages.backends.s3boto3.S3Boto3Storage',
'STORAGE_KWARGS': {
'bucket_name': 'test',
'default_acl': 'public',
'location': 'abc/def'
}
})
def test_video_image_backend_with_params(self):
storage = get_video_image_storage()
self.assertIsInstance(storage, S3Boto3Storage)
self.assertEqual(storage.bucket_name, "test")
self.assertEqual(storage.default_acl, 'public')
self.assertEqual(storage.location, "abc/def")

def test_video_image_without_storages_settings(self):
# Remove VIDEO_IMAGE_SETTINGS from settings safely
if hasattr(settings, 'VIDEO_IMAGE_SETTINGS'):
del settings.VIDEO_IMAGE_SETTINGS

storage = get_video_image_storage()
storage_class = storage.__class__

self.assertEqual(
'django.core.files.storage.filesystem.FileSystemStorage',
f"{storage_class.__module__}.{storage_class.__name__}",
)

def test_video_transcript_backend(self):
# settings file contains the `VIDEO_TRANSCRIPTS_SETTINGS` but dont'have STORAGE_CLASS
# so it returns the default storage.

storage = get_video_transcript_storage()
storage_class = storage.__class__

self.assertEqual(
'django.core.files.storage.filesystem.FileSystemStorage',
f"{storage_class.__module__}.{storage_class.__name__}",
)

@override_settings(VIDEO_TRANSCRIPTS_SETTINGS={
'STORAGE_CLASS': 'storages.backends.s3boto3.S3Boto3Storage',
'STORAGE_KWARGS': {
'bucket_name': 'test',
'default_acl': 'private',
'location': 'abc/'
}
})
def test_transcript_storage_backend_with_params(self):
storage = get_video_transcript_storage()
self.assertIsInstance(storage, S3Boto3Storage)
self.assertEqual(storage.bucket_name, "test")
self.assertEqual(storage.default_acl, 'private')
self.assertEqual(storage.location, 'abc/')

def test_video_transcript_without_storages_settings(self):
# Remove VIDEO_TRANSCRIPTS_SETTINGS from settings.
if hasattr(settings, 'VIDEO_TRANSCRIPTS_SETTINGS'):
del settings.VIDEO_TRANSCRIPTS_SETTINGS

storage = get_video_transcript_storage()
storage_class = storage.__class__

self.assertEqual(
'django.core.files.storage.filesystem.FileSystemStorage',
f"{storage_class.__module__}.{storage_class.__name__}",
)
48 changes: 31 additions & 17 deletions edxval/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.storage import get_storage_class
from django.utils.module_loading import import_string
from fs.path import combine
from pysrt import SubRipFile

Expand Down Expand Up @@ -150,18 +150,39 @@ def video_image_path(video_image_instance, filename): # pylint:disable=unused-a
return '{}{}'.format(settings.VIDEO_IMAGE_SETTINGS.get('DIRECTORY_PREFIX', ''), filename)


def get_configured_storage(settings_key):
"""
Generic function to return a configured Django storage backend
based on the settings dictionary at `settings_key`. This function falls
back to the `default` storage class if there is no `STORAGE_CLASS` entry
under the `setting_key` object.
"""
config = getattr(settings, settings_key, {})
# Retrieve the storage class path and kwargs from the settings
storage_class_path = config.get('STORAGE_CLASS')
options = config.get('STORAGE_KWARGS', {})

# following code only runs for default storages
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'
)

# For Django 5.x, pick options if available
options = getattr(settings, 'STORAGES', {}).get('default', {}).get('OPTIONS', {})

# Import the storage class dynamically
storage_class = import_string(storage_class_path)
return storage_class(**options)


def get_video_image_storage():
"""
Return the configured django storage backend.
"""
if hasattr(settings, 'VIDEO_IMAGE_SETTINGS'):
return get_storage_class(
settings.VIDEO_IMAGE_SETTINGS.get('STORAGE_CLASS'),
)(**settings.VIDEO_IMAGE_SETTINGS.get('STORAGE_KWARGS', {}))

# during edx-platform loading this method gets called but settings are not ready yet
# so in that case we will return default(FileSystemStorage) storage class instance
return get_storage_class()()
return get_configured_storage('VIDEO_IMAGE_SETTINGS')


def video_transcript_path(video_transcript_instance, filename): # pylint:disable=unused-argument
Expand All @@ -179,14 +200,7 @@ def get_video_transcript_storage():
"""
Return the configured django storage backend for video transcripts.
"""
if hasattr(settings, 'VIDEO_TRANSCRIPTS_SETTINGS'):
return get_storage_class(
settings.VIDEO_TRANSCRIPTS_SETTINGS.get('STORAGE_CLASS'),
)(**settings.VIDEO_TRANSCRIPTS_SETTINGS.get('STORAGE_KWARGS', {}))

# during edx-platform loading this method gets called but settings are not ready yet
# so in that case we will return default(FileSystemStorage) storage class instance
return get_storage_class()()
return get_configured_storage('VIDEO_TRANSCRIPTS_SETTINGS')


def create_file_in_fs(file_data, file_name, file_system, static_dir):
Expand Down
6 changes: 3 additions & 3 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
# SERIOUSLY.
#
# ------------------------------
# Generated by edx-lint version: 5.3.7
# Generated by edx-lint version: 5.6.0
# ------------------------------
[MASTER]
ignore = migrations
Expand Down Expand Up @@ -286,7 +286,7 @@ disable =
feature-toggle-needs-doc,
illegal-waffle-usage,

logging-fstring-interpolation,,unicode-format-string, consider-using-with, consider-using-dict-items, django-not-configured, consider-iterating-dictionary, arguments-renamed, no-name-in-module, c-extension-no-member, use-dict-literal
logging-fstring-interpolation,,consider-using-with, consider-using-dict-items, django-not-configured, consider-iterating-dictionary, arguments-renamed, no-name-in-module, c-extension-no-member, use-dict-literal

[REPORTS]
output-format = text
Expand Down Expand Up @@ -383,4 +383,4 @@ int-import-graph =
[EXCEPTIONS]
overgeneral-exceptions = builtins.Exception

# 9e382112fd455855f6d0f08cef2bdb5077da2d63
# 45943ee539dbe5737a3e92f2b981411d8150123b
3 changes: 1 addition & 2 deletions pylintrc_tweaks
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ max-line-length = 120


[MESSAGES CONTROL]
# Disable unicode-format-string
disable+ = ,unicode-format-string, consider-using-with, consider-using-dict-items, django-not-configured, consider-iterating-dictionary, arguments-renamed, no-name-in-module, c-extension-no-member, use-dict-literal
disable+ = ,consider-using-with, consider-using-dict-items, django-not-configured, consider-iterating-dictionary, arguments-renamed, no-name-in-module, c-extension-no-member, use-dict-literal
16 changes: 8 additions & 8 deletions requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@
#
cachetools==5.5.2
# via tox
certifi==2025.1.31
certifi==2025.4.26
# via requests
chardet==5.2.0
# via tox
charset-normalizer==3.4.1
charset-normalizer==3.4.2
# via requests
colorama==0.4.6
# via tox
coverage[toml]==7.6.12
coverage[toml]==7.8.0
# via coveralls
coveralls==4.0.1
# via -r requirements/ci.in
distlib==0.3.9
# via virtualenv
docopt==0.6.2
# via coveralls
filelock==3.17.0
filelock==3.18.0
# via
# tox
# virtualenv
idna==3.10
# via requests
packaging==24.2
packaging==25.0
# via
# pyproject-api
# tox
platformdirs==4.3.6
platformdirs==4.3.7
# via
# tox
# virtualenv
Expand All @@ -42,11 +42,11 @@ pyproject-api==1.9.0
# via tox
requests==2.32.3
# via coveralls
tox==4.24.1
tox==4.25.0
# via -r requirements/ci.in
urllib3==2.2.3
# via
# -c requirements/common_constraints.txt
# requests
virtualenv==20.29.3
virtualenv==20.30.0
# via tox
5 changes: 0 additions & 5 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,3 @@

# Common constraints for edx repos
-c common_constraints.txt

# 5.4.0 is breaking for Python 3.8 and 3.11 CI checks with error
# importlib.resources' has no attribute 'files'
# To be unpinned once edx-val moves to Python 3.12
edx-lint==5.3.7
Loading
Loading