-
Notifications
You must be signed in to change notification settings - Fork 4.2k
feat: create Import and related models #36515
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
Merged
kdmccormick
merged 9 commits into
openedx:master
from
raccoongang:NiedielnitsevIvan/feat/import-from-modulestore-models
Apr 17, 2025
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a0490e5
feat: create Import and related models
NiedielnitsevIvan 17bfd8c
docs: add README
NiedielnitsevIvan 281d2bb
style: remove extra imports
NiedielnitsevIvan 1907dc9
chore: upgrade openedx-learning version
NiedielnitsevIvan 874ba08
refactor: delete extra methods and fix code style issues
NiedielnitsevIvan 2f47a2d
feat: add source_usage_key field to StagedContentForImport
NiedielnitsevIvan 0bd3aaf
refactor: change import statuses and relations to DraftChangeLog
NiedielnitsevIvan d9c3959
style: remove extra imports
NiedielnitsevIvan e3340b0
chore: bump openedx-learning version
NiedielnitsevIvan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| ======================== | ||
| Import from Modulestore | ||
| ======================== | ||
|
|
||
| The new Django application `import_from_modulestore` is designed to | ||
| automate the process of importing course legacy OLX content from Modulestore | ||
| to Content Libraries. The application allows users to easily and quickly | ||
| migrate existing course content, minimizing the manual work and potential | ||
| errors associated with manual migration. | ||
| The new app makes the import process automated and easy to manage. | ||
|
|
||
| The main problems solved by the application: | ||
|
|
||
| * Reducing the time to import course content. | ||
| * Ensuring data integrity during the transfer. | ||
| * Ability to choose which content to import before the final import. | ||
|
|
||
| ------------------------------ | ||
| Import from Modulestore Usage | ||
| ------------------------------ | ||
|
|
||
| * Import course elements at the level of sections, subsections, units, | ||
| and xblocks into the Content Libraries. | ||
| * Choose the structure of this import, whether it will be only xblocks | ||
| from a particular course or full sections/subsections/units. | ||
| * Store the history of imports. | ||
| * Synchronize the course content with the library content (when re-importing, | ||
| the blocks can be updated according to changes in the original course). | ||
| * The new import mechanism ensures data integrity at the time of importing | ||
| by saving the course in StagedContent. | ||
| * Importing the legacy library content into the new Content Libraries. |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| """ | ||
| This module contains the admin configuration for the Import model. | ||
| """ | ||
| from django.contrib import admin | ||
|
|
||
| from .models import Import, PublishableEntityImport, PublishableEntityMapping | ||
|
|
||
|
|
||
| class ImportAdmin(admin.ModelAdmin): | ||
| """ | ||
| Admin configuration for the Import model. | ||
| """ | ||
|
|
||
| list_display = ( | ||
| 'uuid', | ||
| 'created', | ||
| 'status', | ||
| 'source_key', | ||
| 'target_change', | ||
| ) | ||
| list_filter = ( | ||
| 'status', | ||
| ) | ||
| search_fields = ( | ||
| 'source_key', | ||
| 'target_change', | ||
| ) | ||
|
|
||
| raw_id_fields = ('user',) | ||
| readonly_fields = ('status',) | ||
|
|
||
|
|
||
| admin.site.register(Import, ImportAdmin) | ||
| admin.site.register(PublishableEntityImport) | ||
| admin.site.register(PublishableEntityMapping) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| """ | ||
| App for importing from the modulestore tools. | ||
| """ | ||
|
|
||
| from django.apps import AppConfig | ||
|
|
||
|
|
||
| class ImportFromModulestoreConfig(AppConfig): | ||
| """ | ||
| App for importing legacy content from the modulestore. | ||
| """ | ||
|
|
||
| name = 'cms.djangoapps.import_from_modulestore' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| """ | ||
| This module contains the data models for the import_from_modulestore app. | ||
| """ | ||
| from django.db.models import TextChoices | ||
| from django.utils.translation import gettext_lazy as _ | ||
|
|
||
|
|
||
| class ImportStatus(TextChoices): | ||
| """ | ||
| The status of this modulestore-to-learning-core import. | ||
| """ | ||
|
|
||
| NOT_STARTED = 'not_started', _('Waiting to stage content') | ||
| STAGING = 'staging', _('Staging content for import') | ||
| STAGING_FAILED = _('Failed to stage content') | ||
| STAGED = 'staged', _('Content is staged and ready for import') | ||
| IMPORTING = 'importing', _('Importing staged content') | ||
| IMPORTING_FAILED = 'importing_failed', _('Failed to import staged content') | ||
| IMPORTED = 'imported', _('Successfully imported content') | ||
| CANCELED = 'canceled', _('Canceled') |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| """ | ||
| Models for the course to library import app. | ||
| """ | ||
|
|
||
| import uuid as uuid_tools | ||
|
|
||
| from django.contrib.auth import get_user_model | ||
| from django.db import models | ||
| from django.utils.translation import gettext_lazy as _ | ||
|
|
||
| from model_utils.models import TimeStampedModel | ||
| from opaque_keys.edx.django.models import ( | ||
| LearningContextKeyField, | ||
| UsageKeyField, | ||
| ) | ||
| from openedx_learning.api.authoring_models import LearningPackage, PublishableEntity | ||
|
|
||
| from .data import ImportStatus | ||
|
|
||
| User = get_user_model() | ||
|
|
||
|
|
||
| class Import(TimeStampedModel): | ||
| """ | ||
| Represents the action of a user importing a modulestore-based course or legacy | ||
| library into a learning-core based learning package (today, that is always a content library). | ||
| """ | ||
|
|
||
| uuid = models.UUIDField(default=uuid_tools.uuid4, editable=False, unique=True) | ||
| status = models.CharField( | ||
| max_length=100, | ||
| choices=ImportStatus.choices, | ||
| default=ImportStatus.NOT_STARTED, | ||
| db_index=True | ||
| ) | ||
| user = models.ForeignKey(User, on_delete=models.CASCADE) | ||
|
|
||
| # Note: For now, this will always be a course key. In the future, it may be a legacy library key. | ||
| source_key = LearningContextKeyField(help_text=_('The modulestore course'), max_length=255, db_index=True) | ||
| target_change = models.ForeignKey(to='oel_publishing.DraftChangeLog', on_delete=models.SET_NULL, null=True) | ||
|
|
||
| class Meta: | ||
| verbose_name = _('Import from modulestore') | ||
| verbose_name_plural = _('Imports from modulestore') | ||
|
|
||
| def __str__(self): | ||
| return f'{self.source_key} → {self.target_change}' | ||
|
|
||
| def set_status(self, status: ImportStatus): | ||
| """ | ||
| Set import status. | ||
| """ | ||
| self.status = status | ||
| self.save() | ||
| if status in [ImportStatus.IMPORTED, ImportStatus.CANCELED]: | ||
| self.clean_related_staged_content() | ||
|
|
||
| def clean_related_staged_content(self) -> None: | ||
| """ | ||
| Clean related staged content. | ||
| """ | ||
| for staged_content_for_import in self.staged_content_for_import.all(): | ||
| staged_content_for_import.staged_content.delete() | ||
|
|
||
|
|
||
| class PublishableEntityMapping(TimeStampedModel): | ||
| """ | ||
| Represents a mapping between a source usage key and a target publishable entity. | ||
| """ | ||
|
|
||
| source_usage_key = UsageKeyField( | ||
| max_length=255, | ||
| help_text=_('Original usage key/ID of the thing that has been imported.'), | ||
| ) | ||
| target_package = models.ForeignKey(LearningPackage, on_delete=models.CASCADE) | ||
| target_entity = models.ForeignKey(PublishableEntity, on_delete=models.CASCADE) | ||
|
|
||
| class Meta: | ||
| unique_together = ('source_usage_key', 'target_package') | ||
|
|
||
| def __str__(self): | ||
| return f'{self.source_usage_key} → {self.target_entity}' | ||
|
|
||
|
|
||
| class PublishableEntityImport(TimeStampedModel): | ||
| """ | ||
| Represents a publishableentity version that has been imported into a learning package (e.g. content library) | ||
|
|
||
| This is a many-to-many relationship between a container version and a course to library import. | ||
| """ | ||
|
|
||
| import_event = models.ForeignKey(Import, on_delete=models.CASCADE) | ||
| resulting_mapping = models.ForeignKey(PublishableEntityMapping, on_delete=models.SET_NULL, null=True, blank=True) | ||
| resulting_change = models.OneToOneField( | ||
| to='oel_publishing.DraftChangeLogRecord', | ||
| # a changelog record can be pruned, which would set this to NULL, but not delete the | ||
| # entire import record | ||
| null=True, | ||
| on_delete=models.SET_NULL, | ||
| ) | ||
|
|
||
| class Meta: | ||
| unique_together = ( | ||
| ('import_event', 'resulting_mapping'), | ||
| ) | ||
|
|
||
| def __str__(self): | ||
| return f'{self.import_event} → {self.resulting_mapping}' | ||
|
|
||
|
|
||
| class StagedContentForImport(TimeStampedModel): | ||
| """ | ||
| Represents m2m relationship between an import and staged content created for that import. | ||
| """ | ||
|
|
||
| import_event = models.ForeignKey( | ||
| Import, | ||
| on_delete=models.CASCADE, | ||
| related_name='staged_content_for_import', | ||
| ) | ||
| staged_content = models.OneToOneField( | ||
| to='content_staging.StagedContent', | ||
| on_delete=models.CASCADE, | ||
| related_name='staged_content_for_import', | ||
| ) | ||
| # Since StagedContent stores all the keys of the saved blocks, this field was added to optimize search. | ||
| source_usage_key = UsageKeyField( | ||
| max_length=255, | ||
| help_text=_( | ||
| 'The original Usage key of the highest-level component that was saved in StagedContent.' | ||
| ), | ||
| ) | ||
|
|
||
| class Meta: | ||
| unique_together = ( | ||
| ('import_event', 'staged_content'), | ||
| ) | ||
|
|
||
| def __str__(self): | ||
| return f'{self.import_event} → {self.staged_content}' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.