Skip to content

Comments

FC-73 Xqu add submission file (Feature: Submission File System - Enhanced File Management for Graded Submissions)#286

Merged
ormsbee merged 3 commits intoopenedx:masterfrom
aulasneo:XQU-add-submission-file
May 19, 2025
Merged

FC-73 Xqu add submission file (Feature: Submission File System - Enhanced File Management for Graded Submissions)#286
ormsbee merged 3 commits intoopenedx:masterfrom
aulasneo:XQU-add-submission-file

Conversation

@leoaulasneo98
Copy link
Contributor

@leoaulasneo98 leoaulasneo98 commented Feb 24, 2025

FC-73 Feature: Submission File System - Enhanced File Management for Graded Submissions

Description

This pull request implements a comprehensive file management system for submissions through the introduction of the SubmissionFile model and its associated SubmissionFileManager. This enhancement supports proper storage, retrieval, and processing of files attached to submissions within the Open edX ecosystem.

Motivation

The existing submission system requires enhanced file handling capabilities to:

  • Provide seamless transition for existing services (xwatcher)
  • Establish reliable file storage with appropriate metadata
  • Integrate seamlessly with the new submission queue architecture

Previously, the XQueue server managed files for submissions that required document attachments. However, this was implemented inefficiently using plain text fields (s3_keys and s3_urls) without proper structure or validation. This approach led to inconsistent file handling, potential security issues, and maintenance challenges as the system scaled.

Key Improvements

Model Enhancements

  • Introduced SubmissionFile model with:
    • Secure file storage mechanisms
    • Proper association with submission queue records
    • Standardized URL generation for grader access
    • Original filename preservation

File Processing System

  • Developed SubmissionFileManager with capabilities for:
    • Processing multiple file formats (bytes, file objects)
    • Robust error handling for file processing issues
    • Consistent URL generation for xqueue compatibility
    • Efficient retrieval methods for external grader integration

Error Handling

  • Implemented comprehensive error handling for:
    • Invalid file objects
    • IO and OS exceptions during file reading
    • Unicode decoding errors
    • Non-standard file formats

Technical Details

File Processing

  • Support for multiple input types including:
    • Raw bytes
    • Django ContentFile objects
    • UploadedFile instances
    • Custom file-like objects with read() methods

Integration Points

  • Seamless connection with ExternalGraderDetail for complete submission workflow
  • Compatible interface for existing xqueue consumers

Testing Strategy

Extensive test coverage includes:

  • Various file input types
  • Complete processing workflow verification
  • Edge case handling for invalid files
  • Error handling for exceptional conditions
  • File retrieval in grader-compatible format

Open edX Compliance

This implementation adheres to Open edX standards through:

  • Modular design with clear separation of concerns
  • Comprehensive test coverage of edge cases
  • Well-documented interfaces
  • Consistent error handling patterns

Performance Considerations

  • Efficient file storage with minimal overhead
  • Optimized database access patterns
  • Careful handling of file objects to prevent memory issues

BREAKING CHANGES: None. Designed for full compatibility with existing systems.

@openedx-webhooks
Copy link

openedx-webhooks commented Feb 24, 2025

Thanks for the pull request, @leoaulasneo98!

This repository is currently maintained by @openedx/committers-edx-submissions.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Feb 24, 2025
@codecov
Copy link

codecov bot commented Feb 24, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.79%. Comparing base (1096759) to head (275b6e7).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #286      +/-   ##
==========================================
+ Coverage   94.43%   94.79%   +0.36%     
==========================================
  Files          18       18              
  Lines        2263     2423     +160     
  Branches       95       99       +4     
==========================================
+ Hits         2137     2297     +160     
  Misses        115      115              
  Partials       11       11              
Flag Coverage Δ
unittests 94.79% <100.00%> (+0.36%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mphilbrick211 mphilbrick211 added the FC Relates to an Axim Funded Contribution project label Feb 24, 2025
@angonz
Copy link

angonz commented Mar 6, 2025

Review should only start after PR #283 is merged.

Copy link

Choose a reason for hiding this comment

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

Update author of cange
Add link to first ADR
Summarize and simplify.

"""
files_urls = {}
for filename, file_obj in files_dict.items():
if not (isinstance(file_obj, (bytes, ContentFile, SimpleUploadedFile)) or hasattr(file_obj, 'read')):
Copy link

Choose a reason for hiding this comment

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

Why check for hasattr(file_obj, 'read')?

logger.warning(f"Invalid file object type for {filename}")
continue

if hasattr(file_obj, 'read'):
Copy link

Choose a reason for hiding this comment

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

Please explain this workflow

@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch from b9ce70a to ee29b2e Compare April 4, 2025 17:43
Copy link

@angonz angonz left a comment

Choose a reason for hiding this comment

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

Ok on my side. Please open for Axim's review.

@leoaulasneo98 leoaulasneo98 marked this pull request as ready for review April 7, 2025 14:15
@angonz angonz requested a review from ormsbee April 16, 2025 12:50
Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

Some comments and questions. I just want to make sure I'm understanding the intentions of this code properly. Thank you.

"""
Model to handle files associated with submissions
"""
uid = models.UUIDField(default=uuid4, editable=False) # legacy S3 key
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
uid = models.UUIDField(default=uuid4, editable=False) # legacy S3 key
uuid = models.UUIDField(default=uuid4, editable=False) # legacy S3 key

A number of other platform models use "uuid" as the conventional name for this field.

return f"/{self.external_grader.queue_name}/{self.uid}"


class SubmissionFileManager:
Copy link
Contributor

Choose a reason for hiding this comment

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

It's confusing to have something called a "xxxxManager" class in a models.py file, when it isn't a Django ORM Manager. Can you make these into api.py functions instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi Dave,
You're right about the naming confusion. Having a "xxxManager" class in models.py that isn't actually a Django ORM Manager is misleading and could cause confusion.

I've refactored this to follow a clearer naming convention:

Renamed SubmissionFileManager to SubmissionFileProcessor and move to api.py, which better describes its actual purpose - processing file operations for submissions rather than managing model queries.
Updated docstrings to reflect this change.
Kept the functionality encapsulated in a class since it maintains state (the external_grader reference) between operations.

This change preserves the existing functionality while making the code more intuitive.

Comment on lines 762 to 767
This method handles various file object types that might be received from Open edX, including:
- Native Open edX FileObjForWebobFiles objects
- Bytes objects
- ContentFile objects
- SimpleUploadedFile objects
- Any object with a 'read' method
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this function need to support all these?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dave, I understand your point. I initially designed this thinking of a scalable solution for various workflows, but I've reconsidered based on the actual needs of the project.
I've simplified the implementation by using standard TypeError instead of custom exceptions and adjusting the validation to focus on the essentials: bytes or objects with a 'read()' method.
Thanks for your feedback that leads us to more straightforward code.

# Validate file object type
if not (isinstance(file_obj, (bytes, ContentFile, SimpleUploadedFile)) or hasattr(file_obj, 'read')):
logger.error(f"Invalid file object type for {filename}")
raise InvalidFileTypeError(f"Invalid file object type for {filename}")
Copy link
Contributor

Choose a reason for hiding this comment

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

Error should say what the type actually was. I also don't think it's really necessary to create a new error for this, since this is exactly what TypeError is for.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right on both counts. I've updated the code, thanks for the feedback.

@property
def xqueue_url(self):
"""
Returns URL in xqueue format: /queue_name/uid
Copy link
Contributor

Choose a reason for hiding this comment

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

What does "xqueue format" mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the format in which the xqueue-watcher receives the files. That is, the format in which the legacy XqueueServer delivers the files. I've corrected this to make it clearer.


class Meta:
indexes = [
models.Index(fields=['external_grader', 'uid']),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we ever look up by just uuid?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we don't currently look up by just uuid. The lookups are always through the external_grader relationship or using both fields. If we need direct uuid lookups in the future, we'll add a separate index for that field.

@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch 2 times, most recently from ebe7b70 to 4de24a8 Compare April 21, 2025 14:31
@leoaulasneo98 leoaulasneo98 requested a review from ormsbee April 21, 2025 14:32
Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

High level security question: Am I understanding correctly that this is storing things in the default Django storages backend, and if so, does that mean it will be publicly readable (though obscured because of the randomly generated UUIDs)?

@leoaulasneo98
Copy link
Contributor Author

High level security question: Am I understanding correctly that this is storing things in the default Django storages backend, and if so, does that mean it will be publicly readable (though obscured because of the randomly generated UUIDs)?

Hi @ormsbee ,
The implemented flow uses Django file fields, which means security policies depend on the Bucket policies and Django configuration and integration. Here's a breakdown for the standard Tutor development setup using Minio:

You can access a resource if you have its complete URL.
You cannot access resources on ports 9000 or 9001 without credentials, except for resources with a complete, valid URL.
Even if you obtain a URL, you'll still need to decode it or know the file extension to access the resource. This is a mechanism inherited from XqueueServerLegacy.
Currently, there are no signature mechanisms to restrict client-only access. This is because the XqueueWatcher has broad capabilities, and the actual image processing is configured by the grader, not the XqueueWatcher itself.

If you wanted to implement a signature mechanism to restrict resource access, you would need to configure both the edX platform and the XqueueWatcher through the grader.
However, @angonz and I have a question arising from your inquiry: the security for delivering the image depends on the bucket being used. Currently, they are stored in the default OpenEdX bucket, which means it adapts to its existing policies. But we want to know if it's appropriate to use this default bucket or if it would be more convenient to use another pre-existing bucket like the ORA-2 bucket (openedx-learning). Could you share your opinion on this point?

image

@leoaulasneo98 leoaulasneo98 requested a review from ormsbee April 25, 2025 15:54
Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

Please also include a version bump as part of this PR. Thank you.

except (IOError, OSError) as e:
logger.error(f"Error reading file {filename}: {e}")
raise FileProcessingError(f"Error reading file {filename}: {e}") from e
except UnicodeDecodeError as e:
Copy link
Contributor

Choose a reason for hiding this comment

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

The code above is saying that read() returns a bytes object, but the fact that it's capable of throwing a UnicodeDecodeError would imply that it can sometimes return a str.

Copy link
Contributor

Choose a reason for hiding this comment

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

I get that you're trying to be permissive here to make this friendlier for use in the platform, but please just require that the files here be instances of Django's File object (including subclasses), and raise a TypeError if you find ones that are not. Let the caller worry about making sure the data exists there, and let it raise the exception when saving to SubmisssionFile if anything goes wrong. If someone has a bytes instance, it's easy enough for them to convert that to a ContentFile before calling this function. This "is it bytes or str or file" stuff is cluttering up the logic here without really giving much benefit, and bugs are more likely to slip in because of it.


# Convert bytes to ContentFile
# The read() method from file-like objects returns bytes, which we handle here
if isinstance(file_obj, bytes):
Copy link
Contributor

Choose a reason for hiding this comment

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

If file_obj is a str here, then we can pass through this without ever making it into a ContentFile.

@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch 2 times, most recently from 1e49156 to affcaae Compare May 6, 2025 12:36
@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch 3 times, most recently from b36719a to d154a88 Compare May 6, 2025 12:52
@leoaulasneo98 leoaulasneo98 marked this pull request as draft May 6, 2025 13:00
@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch 4 times, most recently from 0836a82 to 40f2a30 Compare May 6, 2025 13:31
@leoaulasneo98 leoaulasneo98 marked this pull request as ready for review May 6, 2025 13:35
@leoaulasneo98 leoaulasneo98 requested a review from ormsbee May 6, 2025 13:35
@mphilbrick211 mphilbrick211 moved this from Waiting on Author to In Eng Review in Contributions May 12, 2025
@sarina
Copy link
Contributor

sarina commented May 13, 2025

@leoaulasneo98 could you please rebase and resolve conflicts?

@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch from 40f2a30 to 3ad53d6 Compare May 13, 2025 15:24
@leoaulasneo98
Copy link
Contributor Author

@leoaulasneo98 could you please rebase and resolve conflicts?

Hi Sarina, good afternoon. Ready.

Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

Thank you for addressing my concerns about bytes/file input handling.

Two blockers on this at the moment:

  1. Storage configuration, which I've tried to give more explicit instructions about.
  2. It looks like all the requirements files have been changed to have absolute paths from your local environment.

Would you like to schedule some time on Friday to go through any of this together?

Thank you.

Comment on lines 722 to 727
def __new__(cls, *args, **kwargs):
if 'pytest' in sys.modules or any('test' in arg for arg in sys.argv):
return FileSystemStorage(*args, **kwargs)
return super().__new__(cls) # pragma: no cover

bucket_name = getattr(settings, "FILE_UPLOAD_STORAGE_BUCKET_NAME", "openedx")
Copy link
Contributor

Choose a reason for hiding this comment

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

However, @angonz and I have a question arising from your inquiry: the security for delivering the image depends on the bucket being used. Currently, they are stored in the default OpenEdX bucket, which means it adapts to its existing policies. But we want to know if it's appropriate to use this default bucket or if it would be more convenient to use another pre-existing bucket like the ORA-2 bucket (openedx-learning). Could you share your opinion on this point?

Sorry for not replying to this in my last review pass. Let's do this: have it look for a specific named storages defined like how openedx-learning does it, but named EDX_SUBMISSIONS['MEDIA']. If that configuration value is not found (either EDX_SUBMISSIONS doesn't exist at all or EDX_SUBMISSIONS['MEDIA'] doesn't), have it fall back to using the default Django storages. Please remember that you can set the storage parameter of a FileField to be any callable that returns a Storage class, so you can switch between storage configurations that way.

So please do the following:

  1. Make a new function get_storage() and use the functools.cache decorator on it to cache it for future use.
  2. Have get_storage() search for EDX_SUBMISSIONS configuration for the storage, but have it also gracefully fall back to the default storage backend if none is configured.
  3. Make sure that get_storage() loads the storage configuration dynamically like this, and don't hardcode it to any specific storage type at this layer.
  4. When creating the SubmissionFile.file FileField, set storage=get_storage.

As a general rule, try not to do smart switching behavior at this layer. Aside from making it hard to configure and override at the settings layer for testing, it's also just really confusing to have the __new__ on a class return an instance of an entirely different class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've already applied the changes. It's made things much easier. Thanks for the help.

Comment on lines 17 to 18
# -r /Users/leonardoberoes/Documents/Aulasneo/Projects/edx-submissions/requirements/base.txt
# -r /Users/leonardoberoes/Documents/Aulasneo/Projects/edx-submissions/requirements/docs.txt
Copy link
Contributor

Choose a reason for hiding this comment

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

These requirements shouldn't have your environment specific paths in them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, Dave. This was generated by compile requirements. It's also no longer necessary to add dependencies with the new approach.

Comment on lines 6 to 15
# This is a temporary solution to override the real common_constraints.txt
# In edx-lint, until the pyjwt constraint in edx-lint has been removed.
# See BOM-2721 for more details.
# Below is the copied and edited version of common_constraints

# This is a temporary solution to override the real common_constraints.txt
# In edx-lint, until the pyjwt constraint in edx-lint has been removed.
# See BOM-2721 for more details.
# Below is the copied and edited version of common_constraints

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like these should just be deleted since they're copies of the first paragraph?

@angonz
Copy link

angonz commented May 15, 2025

Hi Dave, I've sent an invitation for Monday as Friday Leonardo will be out of the office.

@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch from 3ad53d6 to db67756 Compare May 15, 2025 14:59
- Create Submission file model
- Add custom storage to use ORA bucket
- Develop SubmissionFileProcessor for create files in DB
- Add TestSubmissionFileProcessor with extensive test coverage
   - Verify complete file processing workflow
   - Validation tests for file addition in create_external_grader_detail
- Document architectural decisions with ADR
- Add test queue folder in .gitignore
@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch from db67756 to 355da03 Compare May 15, 2025 15:00
Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

In addition to the code comments below, please also do the following:

  1. Update the version to 3.11.0
  2. When you push your changes, please don't squash your commits. We can squash at the end once everything is approved, but it makes it easier/faster to review I can just "view changes since last review", and that functionality breaks if you squash.

Thank you.

Comment on lines 43 to 51
class FileProcessingError(SubmissionError):
"""
Exception raised when there's an error reading or processing a file.

This exception is raised when file operations fail, such as:
- I/O errors when reading file content
- OS errors during file operations
- Unicode decoding errors when processing file content
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this now that it's no longer being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok Dave, understood


# Then get_storage() will return the S3Boto3Storage instance
"""
return _get_storage_cached() # For performance while keeping this function serializable for migrations
Copy link
Contributor

Choose a reason for hiding this comment

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

Very helpful/informative comment, thank you.

storage_config = edx_submissions_config.get('MEDIA')

if storage_config:
return storage_config
Copy link
Contributor

Choose a reason for hiding this comment

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

Wait, it really works to just pass back the dict directly? I thought we'd have to instantiate the storage class here, like this example. So something like:

# Somewhere much higher:
from django.utils.module_loading import import_string

if storage_config:
    storage_cls = import_string(storage_config['BACKEND'])
    options = storage_config.get('OPTIONS', {})
    return storage_cls(**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.

Hi Dave, I had the same question while reading your previous instructions and preferred to keep things simple. But I completely understand what you're saying, and with that piece of code you've made my job much easier. I had previously tried instantiating the storage class, but the result wasn't as clean as I would have liked, so I went with the solution you reviewed a few days ago.

# In settings
from storages.backends.s3boto3 import S3Boto3Storage
EDX_SUBMISSIONS = {
'MEDIA': S3Boto3Storage(bucket_name='my-bucket')
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I see where my misunderstanding of the code in _get_storage_cached() comes from. We shouldn't actually instantiate or even import the storage class in the settings files, only specify the EDX_SUBMISSIONS['MEDIA'] configuration dict in the standard convention that Django uses for media config, which specifies the path of the class we'll need to load later as a string.

The reason has to do with when these things get loaded in the startups cycle. Settings are often overridden in multiple places (e.g. common.py, prod.py, tutor.py, etc). But there's a certain amount of caching/bootstrapping that happens when these storage backends get imported and initialized. For example, there was this issue:

overhangio/tutor-minio#25

The value for AWS_S3_ENDPOINT_URL set in openedx-development-settings specifies port 9000, and it was being written to Studio and LMS settings correctly. But this is the sequence of what was really happening:

  1. openedx-common-settings first sets AWS_S3_ENDPOINT_URL WITHOUT port
    info, since this is how it's used in prod deployments.
  2. openedx-common-settings imports S3Boto3Storage because it wants to
    make a subclass to use for USER_TASKS_ARTIFACT_STORAGE.
  3. The act of importing that package has the side-effect of initializing
    the Django storages (django.core.files.storage.default_storage).
  4. Because the DEFAULT_FILE_STORAGE class is S3Boto3Storage, it then
    reads the value of AWS_S3_ENDPOINT_URL (which has no port info), and
    sets this as the S3 conneciton information for the S3Boto3Storage
    instance at that time.
  5. openedx-development-settings then comes along and changes the value
    of AWS_S3_ENDPOINT_URL to specify port 9000, but by this point it's
    too late to affect django-storages (though it will confuse folks who
    try to debug).

Leaving it in string form avoids this problem. The settings can get overridden as much as people need, and the storage class won't be instantiated until after it's all been set up properly. Please assume the configuration will be something like:

EDX_SUBMISSIONS = {
    'MEDIA': {
        'BACKEND': 'django.core.files.storage.InMemoryStorage',
        'OPTIONS': {
        }
    }
}

Also, please try specifying such a backend for the test settings, to make sure it works properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Understood Dave, I'll try to have the changes made before the meeting.

The addition of submission file functionality and storage enhancement
warrants a minor version bump following semver conventions.
@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch from 362f437 to d99a8f2 Compare May 19, 2025 17:11
- Replace SubmissionFileProcessor with direct file handling in create_external_grader_detail
- Implement flexible storage configuration via EDX_SUBMISSIONS['MEDIA'] dictionary
- Support dynamic backend loading with proper Django settings conventions
- Add cached storage implementation with serialization-safe public interface
- Configure InMemoryStorage for tests to ensure reliable test execution
- Update tests to accommodate new storage patterns and configuration
- Ensure backward compatibility with existing file operations

This implementation follows Django best practices for storage configuration
while maintaining performance through caching and avoiding serialization
issues with migrations.
@leoaulasneo98 leoaulasneo98 force-pushed the XQU-add-submission-file branch from d99a8f2 to 275b6e7 Compare May 19, 2025 17:15
@leoaulasneo98 leoaulasneo98 requested a review from ormsbee May 19, 2025 17:18
@ormsbee ormsbee merged commit 98be5fc into openedx:master May 19, 2025
19 checks passed
@github-project-automation github-project-automation bot moved this from In Eng Review to Done in Contributions May 19, 2025
# SubmissionError imported so that code importing this api has access
from submissions.errors import ( # pylint: disable=unused-import
ExternalGraderQueueEmptyError,
SubmissionError,

Choose a reason for hiding this comment

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

This removal appears to be causing a failure in edx-platform when upgrading to edx-submissions 3.11:

https://github.com/openedx/edx-platform/actions/runs/15213754304/job/42794120855?pr=36786

Copy link
Contributor

Choose a reason for hiding this comment

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

@timmc-edx: Thank you! @leoaulasneo98: Can you please fix this and do a patch version bump?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi Dave, I'll take care of it. Sorry for the mistake. I'll add the import again and do the corresponding PR.

Choose a reason for hiding this comment

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

I went ahead and created a PR for it: #303

(I'd like to get this merged soon so that edx-platform requirements updates are unblocked -- otherwise, it would be best to temporarily pin the version to <3.11.0 in edx-platform.)

Copy link
Contributor

Choose a reason for hiding this comment

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

I merged #301 and made a 3.11.1 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FC Relates to an Axim Funded Contribution project open-source-contribution PR author is not from Axim or 2U

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

7 participants