This PR is addressed at adding the functionality to import modulestore-based content to the learning-core based learning package. Partof: https://github.com/openedx/frontend-app-authoring/issues/1681
140 lines
4.6 KiB
Python
140 lines
4.6 KiB
Python
"""
|
|
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}'
|