Skip to content

Commit

Permalink
refactor: merge RawContent and TextContent into Content
Browse files Browse the repository at this point in the history
  • Loading branch information
ormsbee committed Feb 6, 2024
1 parent 3355189 commit 40f60a9
Show file tree
Hide file tree
Showing 17 changed files with 586 additions and 542 deletions.
27 changes: 13 additions & 14 deletions olx_importer/management/commands/load_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,20 @@ def create_content(self, static_local_path, now, component_version):
logger.warning(f' Static reference not found: "{real_path}"')
return # Might as well bail if we can't find the file.

raw_content, _created = contents_api.get_or_create_raw_content(
content = contents_api.get_or_create_file_content(
self.learning_package.id,
data_bytes=data_bytes,
data=data_bytes,
mime_type=mime_type,
created=now,
)
components_api.add_content_to_component_version(
component_version,
raw_content_id=raw_content.id,
content_id=content.id,
key=key,
learner_downloadable=True,
)

def import_block_type(self, block_type, now): # , publish_log_entry):
def import_block_type(self, block_type_name, now): # , publish_log_entry):
components_found = 0
components_skipped = 0

Expand All @@ -138,8 +138,8 @@ def import_block_type(self, block_type, now): # , publish_log_entry):
# not fool-proof as it will match static file references that are
# outside of tag declarations as well.
static_files_regex = re.compile(r"""['"]\/static\/(.+?)["'\?]""")
block_data_path = self.course_data_path / block_type
namespace="xblock.v1"
block_data_path = self.course_data_path / block_type_name
block_type = components_api.get_or_create_component_type("xblock.v1", block_type_name)

for xml_file_path in block_data_path.glob("*.xml"):
components_found += 1
Expand All @@ -157,27 +157,26 @@ def import_block_type(self, block_type, now): # , publish_log_entry):
display_name = block_root.attrib.get("display_name", "")
_component, component_version = components_api.create_component_and_version(
self.learning_package.id,
namespace=namespace,
type_name=block_type,
component_type=block_type,
local_key=local_key,
title=display_name,
created=now,
created_by=None,
)

# Create the RawContent entry for the raw data...
data_bytes = xml_file_path.read_bytes()
text_content, _created = contents_api.get_or_create_text_content_from_bytes(
text = xml_file_path.read_text('utf-8')
text_content, _created = contents_api.get_or_create_text_content(
self.learning_package.id,
data_bytes=data_bytes,
mime_type=f"application/vnd.openedx.xblock.v1.{block_type}+xml",
text=text,
mime_type=f"application/vnd.openedx.xblock.v1.{block_type_name}+xml",
created=now,
)
# Add the OLX source text to the ComponentVersion
components_api.add_content_to_component_version(
component_version,
raw_content_id=text_content.pk,
key="source.xml",
content_id=text_content.pk,
key="block.xml",
learner_downloadable=False
)

Expand Down
53 changes: 22 additions & 31 deletions openedx_learning/core/components/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin

from .models import Component, ComponentVersion, ComponentVersionRawContent
from .models import Component, ComponentVersion, ComponentVersionContent


class ComponentVersionInline(admin.TabularInline):
Expand Down Expand Up @@ -48,18 +48,18 @@ class ComponentAdmin(ReadOnlyModelAdmin):
inlines = [ComponentVersionInline]


class RawContentInline(admin.TabularInline):
class ContentInline(admin.TabularInline):
"""
Django admin configuration for RawContent
Django admin configuration for Content
"""
model = ComponentVersion.raw_contents.through
model = ComponentVersion.contents.through

def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset.select_related(
"raw_content",
"raw_content__learning_package",
"raw_content__text_content",
"content",
"content__learning_package",
"content__media_type",
"component_version",
"component_version__publishable_entity_version",
"component_version__component",
Expand All @@ -73,7 +73,7 @@ def get_queryset(self, request):
"rendered_data",
]
readonly_fields = [
"raw_content",
"content",
"format_key",
"format_size",
"rendered_data",
Expand All @@ -85,7 +85,7 @@ def rendered_data(self, cvc_obj):

@admin.display(description="Size")
def format_size(self, cvc_obj):
return filesizeformat(cvc_obj.raw_content.size)
return filesizeformat(cvc_obj.content.size)

@admin.display(description="Key")
def format_key(self, cvc_obj):
Expand All @@ -108,7 +108,7 @@ class ComponentVersionAdmin(ReadOnlyModelAdmin):
"title",
"version_num",
"created",
"raw_contents",
"contents",
]
fields = [
"component",
Expand All @@ -118,7 +118,7 @@ class ComponentVersionAdmin(ReadOnlyModelAdmin):
"created",
]
list_display = ["component", "version_num", "uuid", "created"]
inlines = [RawContentInline]
inlines = [ContentInline]

def get_queryset(self, request):
queryset = super().get_queryset(request)
Expand All @@ -129,12 +129,12 @@ def get_queryset(self, request):
)


def link_for_cvc(cvc_obj: ComponentVersionRawContent) -> str:
def link_for_cvc(cvc_obj: ComponentVersionContent) -> str:
"""
Get the download URL for the given ComponentVersionRawContent instance
Get the download URL for the given ComponentVersionContent instance
"""
return "/media_server/component_asset/{}/{}/{}/{}".format(
cvc_obj.raw_content.learning_package.key,
cvc_obj.content.learning_package.key,
cvc_obj.component_version.component.key,
cvc_obj.component_version.version_num,
cvc_obj.key,
Expand All @@ -151,27 +151,18 @@ def format_text_for_admin_display(text: str) -> SafeText:
)


def content_preview(cvc_obj: ComponentVersionRawContent) -> SafeText:
def content_preview(cvc_obj: ComponentVersionContent) -> SafeText:
"""
Get the HTML to display a preview of the given ComponentVersionRawContent
Get the HTML to display a preview of the given ComponentVersionContent
"""
raw_content_obj = cvc_obj.raw_content
content_obj = cvc_obj.content

if raw_content_obj.media_type.type == "image":
if content_obj.media_type.type == "image":
return format_html(
'<img src="{}" style="max-width: 100%;" />',
# TODO: configure with settings value:
"/media_server/component_asset/{}/{}/{}/{}".format(
cvc_obj.raw_content.learning_package.key,
cvc_obj.component_version.component.key,
cvc_obj.component_version.version_num,
cvc_obj.key,
),
content_obj.file_url(),
)

if hasattr(raw_content_obj, "text_content"):
return format_text_for_admin_display(
raw_content_obj.text_content.text,
)

return format_html("This content type cannot be displayed.")
return format_text_for_admin_display(
content_obj.text,
)
67 changes: 35 additions & 32 deletions openedx_learning/core/components/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,48 @@
from django.db.models import Q, QuerySet
from django.db.transaction import atomic

from ...lib.cache import lru_cache
from ..publishing import api as publishing_api
from .models import Component, ComponentType, ComponentVersion, ComponentVersionRawContent
from .models import Component, ComponentType, ComponentVersion, ComponentVersionContent


@lru_cache(maxsize=128)
def get_or_create_component_type_id(namespace: str, name: str) -> int:
def get_or_create_component_type(namespace: str, name: str) -> ComponentType:
"""
Get the ID of a ComponentType, and create if missing.
Caching Warning: Be careful about putting any caching decorator around this
function (e.g. ``lru_cache``). It's possible that incorrect cache values
could leak out in the event of a rollback–e.g. new types are introduced in
a large import transaction which later fails. You can safely cache the
results that come back from this function with a local dict in your import
process instead.#
"""
component_type, _created = ComponentType.objects.get_or_create(
namespace=namespace,
name=name,
)
return component_type.id
return component_type


def create_component(
learning_package_id: int,
/,
namespace: str,
type_name: str,
component_type: ComponentType,
local_key: str,
created: datetime,
created_by: int | None,
) -> Component:
"""
Create a new Component (an entity like a Problem or Video)
"""
key = f"{namespace}:{type_name}@{local_key}"
key = f"{component_type.namespace}:{component_type.name}@{local_key}"
with atomic():
publishable_entity = publishing_api.create_publishable_entity(
learning_package_id, key, created, created_by
)
component = Component.objects.create(
publishable_entity=publishable_entity,
learning_package_id=learning_package_id,
component_type_id=get_or_create_component_type_id(namespace, type_name),
component_type=component_type,
local_key=local_key,
)
return component
Expand Down Expand Up @@ -144,25 +148,25 @@ def create_next_version(
component_id=component_pk,
)
# First copy the new stuff over...
for key, raw_content_pk in content_to_replace.items():
# If the raw_content_pk is None, it means we want to remove the
for key, content_pk in content_to_replace.items():
# If the content_pk is None, it means we want to remove the
# content represented by our key from the next version. Otherwise,
# we add our key->raw_content_pk mapping to the next version.
if raw_content_pk is not None:
ComponentVersionRawContent.objects.create(
raw_content_id=raw_content_pk,
# we add our key->content_pk mapping to the next version.
if content_pk is not None:
ComponentVersionContent.objects.create(
content_id=content_pk,
component_version=component_version,
key=key,
learner_downloadable=False,
)
# Now copy any old associations that existed, as long as they aren't
# in conflict with the new stuff or marked for deletion.
last_version_content_mapping = ComponentVersionRawContent.objects \
last_version_content_mapping = ComponentVersionContent.objects \
.filter(component_version=last_version)
for cvrc in last_version_content_mapping:
if cvrc.key not in content_to_replace:
ComponentVersionRawContent.objects.create(
raw_content_id=cvrc.raw_content_id,
ComponentVersionContent.objects.create(
content_id=cvrc.content_id,
component_version=component_version,
key=cvrc.key,
learner_downloadable=cvrc.learner_downloadable,
Expand All @@ -174,8 +178,7 @@ def create_next_version(
def create_component_and_version(
learning_package_id: int,
/,
namespace: str,
type_name: str,
component_type: ComponentType,
local_key: str,
title: str,
created: datetime,
Expand All @@ -186,7 +189,7 @@ def create_component_and_version(
"""
with atomic():
component = create_component(
learning_package_id, namespace, type_name, local_key, created, created_by
learning_package_id, component_type, local_key, created, created_by
)
component_version = create_component_version(
component.pk,
Expand Down Expand Up @@ -297,9 +300,9 @@ def get_component_version_content(
component_key: str,
version_num: int,
key: Path,
) -> ComponentVersionRawContent:
) -> ComponentVersionContent:
"""
Look up ComponentVersionRawContent by human readable keys.
Look up ComponentVersionContent by human readable keys.
Can raise a django.core.exceptions.ObjectDoesNotExist error if there is no
matching ComponentVersionRawContent.
Expand All @@ -310,11 +313,11 @@ def get_component_version_content(
& Q(component_version__publishable_entity_version__version_num=version_num)
& Q(key=key)
)
return ComponentVersionRawContent.objects \
return ComponentVersionContent.objects \
.select_related(
"raw_content",
"raw_content__media_type",
"raw_content__textcontent",
"content",
"content__media_type",
"content__textcontent",
"component_version",
"component_version__component",
"component_version__component__learning_package",
Expand All @@ -324,16 +327,16 @@ def get_component_version_content(
def add_content_to_component_version(
component_version_id: int,
/,
raw_content_id: int,
content_id: int,
key: str,
learner_downloadable=False,
) -> ComponentVersionRawContent:
) -> ComponentVersionContent:
"""
Add a RawContent to the given ComponentVersion
Add a Content to the given ComponentVersion
"""
cvrc, _created = ComponentVersionRawContent.objects.get_or_create(
cvrc, _created = ComponentVersionContent.objects.get_or_create(
component_version_id=component_version_id,
raw_content_id=raw_content_id,
content_id=content_id,
key=key,
learner_downloadable=learner_downloadable,
)
Expand Down
Loading

0 comments on commit 40f60a9

Please sign in to comment.