Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
03bc65b
feat: add support for lct in library_context
navinkarkera Apr 17, 2025
157f690
feat: library unit sync
navinkarkera Apr 19, 2025
04adc26
fixup! feat: library unit sync
navinkarkera Apr 20, 2025
ce0686e
fixup! feat: library unit sync
navinkarkera Apr 20, 2025
5549ee4
fixup! feat: library unit sync
navinkarkera Apr 21, 2025
b87bc7b
feat: unit sync using emulated library container usage locator key
navinkarkera Apr 21, 2025
87762b5
Revert "feat: add support for lct in library_context"
navinkarkera Apr 21, 2025
fcfc0ee
Revert "feat: unit sync using emulated library container usage locato…
navinkarkera Apr 22, 2025
8f89cd6
feat: separate managers for component and container upstream sync
navinkarkera Apr 22, 2025
b3c0fc3
fixup! feat: separate managers for component and container upstream sync
navinkarkera Apr 22, 2025
95395cd
feat: remove upstream links from unit child components on sync
navinkarkera Apr 22, 2025
2d5e63e
refactor!: rename publishable entity links table and update upstream_…
navinkarkera Apr 22, 2025
7763b9b
feat: create component link only for component xblocks
navinkarkera Apr 22, 2025
bcb88fe
feat: container link model
navinkarkera Apr 23, 2025
4442dac
chore: fix lint issues
navinkarkera Apr 23, 2025
ce1247a
temp: point opaque keys to dev branch
navinkarkera Apr 23, 2025
16ae3b4
fix: mypy errors
navinkarkera Apr 23, 2025
b8eb171
fix: import lint errors
navinkarkera Apr 23, 2025
e0c8662
fix: failing tests
navinkarkera Apr 23, 2025
78bbb41
fix: doc lint issues
navinkarkera Apr 23, 2025
b1ba9e8
fix: lint issues
navinkarkera Apr 23, 2025
fe316a1
fix: failing tests
navinkarkera Apr 23, 2025
3745db7
Revert "fix: failing tests"
navinkarkera Apr 23, 2025
18ee10f
feat: update downstream api views
navinkarkera Apr 23, 2025
0051e18
feat: delete extra components in container on sync (not working)
navinkarkera Apr 23, 2025
ef8b288
chore: update with latest master
bradenmacdonald Apr 23, 2025
3040b1e
chore: update with latest master
bradenmacdonald Apr 23, 2025
c9ebceb
revert: changes to xblock runtime
bradenmacdonald Apr 23, 2025
9439310
chore: cleanups
bradenmacdonald Apr 23, 2025
6ac9f6f
revert: changes to upstream_sync.py
bradenmacdonald Apr 23, 2025
29f8f7c
feat: WIP toward syncing
bradenmacdonald Apr 24, 2025
eb81834
fix: duplicate definitions of LibraryXBlockMetadata
bradenmacdonald Apr 24, 2025
2859bc1
fix: type issues
bradenmacdonald Apr 24, 2025
3d11691
test: fix tests
bradenmacdonald Apr 24, 2025
406ce79
fix: duplicate definitions of LibraryXBlockMetadata (fixup)
bradenmacdonald Apr 24, 2025
84d0485
chore: quality issues
bradenmacdonald Apr 24, 2025
b273796
test: update tests
bradenmacdonald Apr 24, 2025
f613566
test: add a new integration test suite for syncing
bradenmacdonald Apr 24, 2025
b71f11a
test: continue building out integration tests
bradenmacdonald Apr 24, 2025
f0e5556
feat: partially implement container+child syncing
bradenmacdonald Apr 24, 2025
3dfcc26
test: continue building out integration tests
bradenmacdonald Apr 24, 2025
7ac5109
test: continue building out integration tests
bradenmacdonald Apr 24, 2025
bf39a3e
fix: blockserializer wasn't always serializing all HTML block fields
bradenmacdonald Apr 24, 2025
a214cc4
test: continue building out integration tests
bradenmacdonald Apr 24, 2025
a20baec
chore: quality fix
bradenmacdonald Apr 24, 2025
ecdeafd
chore: updated query count in test, from serializer/dataclass change
bradenmacdonald Apr 24, 2025
4d090e3
fix: import lint issue
navinkarkera Apr 24, 2025
ef20d1b
Merge branch 'master' into navin/fal-4077/link-unit
navinkarkera Apr 24, 2025
a452cf6
fix: missing field in library xblock metadata
navinkarkera Apr 24, 2025
144a8b1
test: update upstream sync error message in tests
navinkarkera Apr 24, 2025
00f02e2
revert: delete_item api changes
navinkarkera Apr 24, 2025
b05de6d
feat: handle reorder, addition and deletion of components in sync
navinkarkera Apr 24, 2025
8762ae0
chore: fix unrelated conflict
navinkarkera Apr 24, 2025
45e9a34
fix: lint issues
navinkarkera Apr 24, 2025
f111bf6
feat: return unit upstreamInfo and disallow edits to units in courses…
ChrisChV Apr 24, 2025
4a69f30
docs: capitalization of XBlock
bradenmacdonald Apr 24, 2025
959eadd
refactor: (minor) change python property name to reflect type better
bradenmacdonald Apr 24, 2025
fbbae0b
fix: lots of "Tried to inspect a missing...upstream link" warnings
bradenmacdonald Apr 24, 2025
7d301a6
docs: mention potential REST API for future refactor
bradenmacdonald Apr 24, 2025
ea13143
fix: check if upstream actually exists before making unit read-only
bradenmacdonald Apr 24, 2025
3f14cbf
chore: fix camel-case var
bradenmacdonald Apr 24, 2025
affff41
fix: test failure when mocked XBlock doesn't have UpstreamSyncMixin
bradenmacdonald Apr 24, 2025
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
53 changes: 46 additions & 7 deletions cms/djangoapps/contentstore/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
from cms.djangoapps.contentstore.models import (
BackfillCourseTabsConfig,
CleanStaleCertificateAvailabilityDatesConfig,
ComponentLink,
ContainerLink,
LearningContextLinksStatus,
PublishableEntityLink,
VideoUploadConfig
VideoUploadConfig,
)
from cms.djangoapps.contentstore.outlines_regenerate import CourseOutlineRegenerate
from openedx.core.djangoapps.content.learning_sequences.api import key_supports_outlines

from .tasks import update_outline_from_modulestore_task, update_all_outlines_from_modulestore_task

from .tasks import update_all_outlines_from_modulestore_task, update_outline_from_modulestore_task

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -88,10 +88,10 @@ class CleanStaleCertificateAvailabilityDatesConfigAdmin(ConfigurationModelAdmin)
pass


@admin.register(PublishableEntityLink)
class PublishableEntityLinkAdmin(admin.ModelAdmin):
@admin.register(ComponentLink)
class ComponentLinkAdmin(admin.ModelAdmin):
"""
PublishableEntityLink admin.
ComponentLink admin.
"""
fields = (
"uuid",
Expand Down Expand Up @@ -127,6 +127,45 @@ def has_change_permission(self, request, obj=None):
return False


@admin.register(ContainerLink)
class ContainerLinkAdmin(admin.ModelAdmin):
"""
ContainerLink admin.
"""
fields = (
"uuid",
"upstream_container",
"upstream_container_key",
"upstream_context_key",
"downstream_usage_key",
"downstream_context_key",
"version_synced",
"version_declined",
"created",
"updated",
)
readonly_fields = fields
list_display = [
"upstream_container",
"upstream_container_key",
"downstream_usage_key",
"version_synced",
"updated",
]
search_fields = [
"upstream_container_key",
"upstream_context_key",
"downstream_usage_key",
"downstream_context_key",
]

def has_add_permission(self, request):
return False

def has_change_permission(self, request, obj=None):
return False


@admin.register(LearningContextLinksStatus)
class LearningContextLinksStatusAdmin(admin.ModelAdmin):
"""
Expand Down
33 changes: 29 additions & 4 deletions cms/djangoapps/contentstore/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
)

from cms.djangoapps.models.settings.course_grading import CourseGradingModel
from cms.lib.xblock.upstream_sync import UpstreamLink, UpstreamLinkException, fetch_customizable_fields
from cms.lib.xblock.upstream_sync import UpstreamLink, UpstreamLinkException
from cms.lib.xblock.upstream_sync_block import fetch_customizable_fields_from_block
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
import openedx.core.djangoapps.content_staging.api as content_staging_api
import openedx.core.djangoapps.content_tagging.api as content_tagging_api
Expand Down Expand Up @@ -416,7 +417,7 @@ def _fetch_and_set_upstream_link(
user: User
):
"""
Fetch and set upstream link for the given xblock. This function handles following cases:
Fetch and set upstream link for the given xblock which is being pasted. This function handles following cases:
* the xblock is copied from a v2 library; the library block is set as upstream.
* the xblock is copied from a course; no upstream is set, only copied_from_block is set.
* the xblock is copied from a course where the source block was imported from a library; the original libary block
Expand All @@ -425,7 +426,7 @@ def _fetch_and_set_upstream_link(
# Try to link the pasted block (downstream) to the copied block (upstream).
temp_xblock.upstream = copied_from_block
try:
UpstreamLink.get_for_block(temp_xblock)
upstream_link = UpstreamLink.get_for_block(temp_xblock)
except UpstreamLinkException:
# Usually this will fail. For example, if the copied block is a modulestore course block, it can't be an
# upstream. That's fine! Instead, we store a reference to where this block was copied from, in the
Expand Down Expand Up @@ -456,7 +457,8 @@ def _fetch_and_set_upstream_link(
# later wants to restore it, it will restore to the value that the field had when the block was pasted. Of
# course, if the author later syncs updates from a *future* published upstream version, then that will fetch
# new values from the published upstream content.
fetch_customizable_fields(upstream=temp_xblock, downstream=temp_xblock, user=user)
if isinstance(upstream_link.upstream_key, UsageKey): # only if upstream is a block, not a container
fetch_customizable_fields_from_block(downstream=temp_xblock, user=user, upstream=temp_xblock)


def _import_xml_node_to_parent(
Expand Down Expand Up @@ -790,3 +792,26 @@ def _get_usage_key_from_node(node, parent_id: str) -> UsageKey | None:
)

return usage_key


def concat_static_file_notices(notices: list[StaticFileNotices]) -> StaticFileNotices:
"""Combines multiple static file notices into a single object

Args:
notices: list of StaticFileNotices

Returns:
Single StaticFileNotices
"""
new_files = []
conflicting_files = []
error_files = []
for notice in notices:
new_files.extend(notice.new_files)
conflicting_files.extend(notice.conflicting_files)
error_files.extend(notice.error_files)
return StaticFileNotices(
new_files=list(set(new_files)),
conflicting_files=list(set(conflicting_files)),
error_files=list(set(error_files)),
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Management command to recreate upstream-dowstream links in PublishableEntityLink for course(s).
Management command to recreate upstream-dowstream links in ComponentLink for course(s).

This command can be run for all the courses or for given list of courses.
"""
Expand All @@ -23,7 +23,7 @@

class Command(BaseCommand):
"""
Recreate links for course(s) in PublishableEntityLink table.
Recreate upstream links for course(s) in ComponentLink and ContainerLink tables.

Examples:
# Recreate upstream links for two courses.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.20 on 2025-04-22 15:08
import uuid

import django.db.models.deletion
import opaque_keys.edx.django.models
import openedx_learning.lib.fields
import openedx_learning.lib.validators
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
('oel_components', '0003_remove_componentversioncontent_learner_downloadable'),
('contentstore', '0009_learningcontextlinksstatus_publishableentitylink'),
]

operations = [
migrations.RenameModel(
old_name='PublishableEntityLink',
new_name='ComponentLink',
),
migrations.AlterModelOptions(
name='componentlink',
options={'verbose_name': 'Component Link', 'verbose_name_plural': 'Component Links'},
),
migrations.AlterField(
model_name='componentlink',
name='upstream_block',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='links',
to='oel_components.component',
),
),
migrations.CreateModel(
name='ContainerLink',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
('upstream_context_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_index=True, help_text='Upstream context key i.e., learning_package/library key', max_length=500)),
('downstream_usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255, unique=True)),
('downstream_context_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)),
('version_synced', models.IntegerField()),
('version_declined', models.IntegerField(blank=True, null=True)),
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
('upstream_container_key', opaque_keys.edx.django.models.ContainerKeyField(help_text='Upstream block key (e.g. lct:...), this value cannot be null and is useful to track upstream library blocks that do not exist yet or were deleted.', max_length=255)),
('upstream_container', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='oel_publishing.container')),
],
options={
'abstract': False,
'verbose_name': 'Container Link',
'verbose_name_plural': 'Container Links',
},
),
]
Loading
Loading