Skip to content

Commit

Permalink
Merge branch 'open-release/quince.master' into keblysh/feat/correct-i…
Browse files Browse the repository at this point in the history
…mage-for-fb-sharing-quince.master
  • Loading branch information
vladislavkeblysh authored Apr 23, 2024
2 parents 880a0ca + a6bd0e2 commit 522d2a3
Show file tree
Hide file tree
Showing 38 changed files with 323 additions and 111 deletions.
11 changes: 9 additions & 2 deletions cms/djangoapps/contentstore/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
from xmodule.library_content_block import LibraryContentBlock
from xmodule.modulestore.django import modulestore
from xmodule.xml_block import XmlMixin

Expand Down Expand Up @@ -336,8 +337,14 @@ def _import_xml_node_to_parent(
new_xblock = store.update_item(temp_xblock, user_id, allow_not_found=True)
parent_xblock.children.append(new_xblock.location)
store.update_item(parent_xblock, user_id)
for child_node in child_nodes:
_import_xml_node_to_parent(child_node, new_xblock, store, user_id=user_id)
if isinstance(new_xblock, LibraryContentBlock):
# Special case handling for library content. If we need this for other blocks in the future, it can be made into
# an API, and we'd call new_block.studio_post_paste() instead of this code.
# In this case, we want to pull the children from the library and let library_tools assign their IDs.
new_xblock.tools.update_children(new_xblock, version=new_xblock.source_library_version)
else:
for child_node in child_nodes:
_import_xml_node_to_parent(child_node, new_xblock, store, user_id=user_id)
return new_xblock


Expand Down
86 changes: 83 additions & 3 deletions cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
APIs.
"""
import ddt
from django.test import LiveServerTestCase
from opaque_keys.edx.keys import UsageKey
from rest_framework.test import APIClient
from xmodule.modulestore.django import contentstore
from xmodule.modulestore.django import contentstore, modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, upload_file_to_course
from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, ToyCourseFactory
from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, LibraryFactory, ToyCourseFactory

from cms.djangoapps.contentstore.utils import reverse_usage_url
from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient

CLIPBOARD_ENDPOINT = "/api/content-staging/v1/clipboard/"
XBLOCK_ENDPOINT = "/xblock/"
Expand Down Expand Up @@ -109,7 +113,7 @@ def test_copy_and_paste_component(self, block_args):
"""
Test copying a component (XBlock) from one course into another
"""
source_course = CourseFactory.create(display_name='Destination Course')
source_course = CourseFactory.create(display_name='Source Course')
source_block = BlockFactory.create(parent_location=source_course.location, **block_args)

dest_course = CourseFactory.create(display_name='Destination Course')
Expand Down Expand Up @@ -204,3 +208,79 @@ def test_paste_with_assets(self):
source_pic2_hash = contentstore().find(source_course.id.make_asset_key("asset", "picture2.jpg")).content_digest
dest_pic2_hash = contentstore().find(dest_course_key.make_asset_key("asset", "picture2.jpg")).content_digest
assert source_pic2_hash != dest_pic2_hash # Because there was a conflict, this file was unchanged.


class ClipboardLibraryContentPasteTestCase(LiveServerTestCase, ModuleStoreTestCase):
"""
Test Clipboard Paste functionality with library content
"""

def setUp(self):
"""
Set up a v2 Content Library and a library content block
"""
super().setUp()
self.client = AjaxEnabledTestClient()
self.client.login(username=self.user.username, password=self.user_password)
self.store = modulestore()

def test_paste_library_content_block_v1(self):
"""
Same as the above test, but uses modulestore (v1) content library
"""
library = LibraryFactory.create()
data = {
'parent_locator': str(library.location),
'category': 'html',
'display_name': 'HTML Content',
}
response = self.client.ajax_post(XBLOCK_ENDPOINT, data)
self.assertEqual(response.status_code, 200)
course = CourseFactory.create(display_name='Course')
orig_lc_block = BlockFactory.create(
parent=course,
category="library_content",
source_library_id=str(library.location.library_key),
display_name="LC Block",
publish_item=False,
)
orig_lc_block.refresh_children()
orig_child = self.store.get_item(orig_lc_block.children[0])
assert orig_child.display_name == "HTML Content"
# Copy a library content block that has children:
copy_response = self.client.post(CLIPBOARD_ENDPOINT, {
"usage_key": str(orig_lc_block.location)
}, format="json")
assert copy_response.status_code == 200

# Paste the Library content block:
paste_response = self.client.ajax_post(XBLOCK_ENDPOINT, {
"parent_locator": str(course.location),
"staged_content": "clipboard",
})
assert paste_response.status_code == 200
dest_lc_block_key = UsageKey.from_string(paste_response.json()["locator"])

# Get the ID of the new child:
dest_lc_block = self.store.get_item(dest_lc_block_key)
dest_child = self.store.get_item(dest_lc_block.children[0])
assert dest_child.display_name == "HTML Content"

# Importantly, the ID of the child must not changed when the library content is synced.
# Otherwise, user state saved against this child will be lost when it syncs.
dest_lc_block.refresh_children()
updated_dest_child = self.store.get_item(dest_lc_block.children[0])
assert dest_child.location == updated_dest_child.location

def _sync_lc_block_from_library(self, attr_name):
"""
Helper method to "sync" a Library Content Block by [re-]fetching its
children from the library.
"""
usage_key = getattr(self, attr_name).location
# It's easiest to do this via the REST API:
handler_url = reverse_usage_url('preview_handler', usage_key, kwargs={'handler': 'upgrade_and_sync'})
response = self.client.post(handler_url)
assert response.status_code == 200
# Now reload the block and make sure the child is in place
setattr(self, attr_name, self.store.get_item(usage_key)) # we must reload after upgrade_and_sync
6 changes: 6 additions & 0 deletions cms/static/sass/elements/_system-feedback.scss
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@

.action-secondary {
@extend %t-action4;
cursor: pointer;
color: $white;

&:hover {
color: $gray-l3;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion cms/templates/widgets/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ <h3 class="title"><span class="label">${_("Tools")}</span> <span class="icon fa
% if user.is_authenticated:
<input title="preference api" type="hidden" id="preference-api-url" class="url-endpoint" value="${reverse('preferences_api', kwargs={'username': user.username})}" data-user-is-authenticated="true">
% else:
<input title="session update url" type="hidden" id="update-session-url" class="url-endpoint" value="${reverse('session_language')}" data-user-is-authenticated="false">
<input title="session update url" type="hidden" id="update-session-url" class="url-endpoint" value="${reverse('update_language')}" data-user-is-authenticated="false">
% endif
<label><span class="sr">${_("Choose Language")}</span>
<select class="input select language-selector" id="settings-language-value" name="language">
Expand Down
4 changes: 2 additions & 2 deletions common/test/data/course_after_rename/about/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Requirements</h2>
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #1" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #1" />
</div>

<h3>Staff Member #1</h3>
Expand All @@ -23,7 +23,7 @@ <h3>Staff Member #1</h3>

<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #2" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #2" />
</div>

<h3>Staff Member #2</h3>
Expand Down
4 changes: 2 additions & 2 deletions common/test/data/course_before_rename/about/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Requirements</h2>
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #1" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #1" />
</div>

<h3>Staff Member #1</h3>
Expand All @@ -23,7 +23,7 @@ <h3>Staff Member #1</h3>

<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #2" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #2" />
</div>

<h3>Staff Member #2</h3>
Expand Down
4 changes: 2 additions & 2 deletions common/test/data/course_info_updates/about/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Prerequisites</h2>
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #1" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #1" />
</div>

<h3>Staff Member #1</h3>
Expand All @@ -23,7 +23,7 @@ <h3>Staff Member #1</h3>

<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #2" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #2" />
</div>

<h3>Staff Member #2</h3>
Expand Down
4 changes: 2 additions & 2 deletions common/test/data/manual-testing-complete/about/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Prerequisites</h2>
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #1" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #1" />
</div>

<h3>Staff Member #1</h3>
Expand All @@ -23,7 +23,7 @@ <h3>Staff Member #1</h3>

<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #2" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #2" />
</div>

<h3>Staff Member #2</h3>
Expand Down
4 changes: 2 additions & 2 deletions common/test/data/scoreable/about/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Requirements</h2>
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #1" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #1" />
</div>

<h3>Staff Member #1</h3>
Expand All @@ -23,7 +23,7 @@ <h3>Staff Member #1</h3>

<article class="teacher">
<div class="teacher-image">
<img src="/static/images/placeholder-faculty.png" align="left" style="margin: 0 20px 0;" alt="Course Staff Image #2" />
<img src="/static/images/placeholder-faculty.png" align="left" alt="Course Staff Image #2" />
</div>

<h3>Staff Member #2</h3>
Expand Down
16 changes: 16 additions & 0 deletions lms/djangoapps/certificates/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,19 @@ def test_html_view_site_configuration_missing(self):
response,
'This should not survive being overwritten by static content',
)

@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED, GOOGLE_ANALYTICS_4_ID='GA-abc')
@with_site_configuration(configuration={'platform_name': 'My Platform Site'})
def test_html_view_with_g4(self):
test_url = get_certificate_url(
user_id=self.user.id,
course_id=str(self.course.id),
uuid=self.cert.verify_uuid
)
self._add_course_certificates(count=1, signatory_count=2)
response = self.client.get(test_url)
self.assertContains(
response,
'awarded this My Platform Site Honor Code Certificate of Completion',
)
self.assertContains(response, 'googletagmanager')
9 changes: 9 additions & 0 deletions lms/djangoapps/course_home_api/course_metadata/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ def get_url(self, tab):
return request.build_absolute_uri(tab.link_func(self.context.get('course'), reverse))


class CourseModeSerrializer(serializers.Serializer):
"""
Serializer for the Course Mode
"""
slug = serializers.CharField()
name = serializers.CharField()


class CourseHomeMetadataSerializer(VerifiedModeSerializer):
"""
Serializer for the Course Home Course Metadata
Expand All @@ -48,3 +56,4 @@ class CourseHomeMetadataSerializer(VerifiedModeSerializer):
username = serializers.CharField()
user_timezone = serializers.CharField()
can_view_certificate = serializers.BooleanField()
course_modes = CourseModeSerrializer(many=True)
4 changes: 4 additions & 0 deletions lms/djangoapps/course_home_api/course_metadata/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.core.djangoapps.courseware_api.utils import get_celebrations_dict

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.course_api.api import course_detail
from lms.djangoapps.course_goals.models import UserActivity
Expand Down Expand Up @@ -117,6 +118,8 @@ def get(self, request, *args, **kwargs):
# Record course goals user activity for (web) learning mfe course tabs
UserActivity.record_user_activity(request.user, course_key)

course_modes = CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False)

data = {
'course_id': course.id,
'username': username,
Expand All @@ -133,6 +136,7 @@ def get(self, request, *args, **kwargs):
'celebrations': celebrations,
'user_timezone': user_timezone,
'can_view_certificate': certificates_viewable_for_course(course),
'course_modes': course_modes,
}
context = self.get_serializer_context()
context['course'] = course
Expand Down
1 change: 1 addition & 0 deletions lms/djangoapps/course_home_api/progress/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,4 @@ class ProgressTabSerializer(VerifiedModeSerializer):
username = serializers.CharField()
user_has_passing_grade = serializers.BooleanField()
verification_data = VerificationDataSerializer()
disable_progress_graph = serializers.BooleanField()
2 changes: 2 additions & 0 deletions lms/djangoapps/course_home_api/progress/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def get(self, request, *args, **kwargs):

block = modulestore().get_course(course_key)
grading_policy = block.grading_policy
disable_progress_graph = block.disable_progress_graph
verification_status = IDVerificationService.user_status(student)
verification_link = None
if verification_status['status'] is None or verification_status['status'] == 'expired':
Expand Down Expand Up @@ -259,6 +260,7 @@ def get(self, request, *args, **kwargs):
'username': username,
'user_has_passing_grade': user_has_passing_grade,
'verification_data': verification_data,
'disable_progress_graph': disable_progress_graph,
}
context = self.get_serializer_context()
context['staff_access'] = is_staff
Expand Down
10 changes: 9 additions & 1 deletion lms/djangoapps/ora_staff_grader/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# pylint: disable=abstract-method
# pylint: disable=missing-function-docstring

from urllib.parse import urljoin
from django.conf import settings
from rest_framework import serializers

from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
Expand Down Expand Up @@ -191,11 +193,17 @@ def get_isEnabled(self, obj):
class UploadedFileSerializer(serializers.Serializer):
"""Serializer for a file uploaded as a part of a response"""

downloadUrl = serializers.URLField(source="download_url")
downloadUrl = serializers.SerializerMethodField(method_name="get_download_url")
description = serializers.CharField()
name = serializers.CharField()
size = serializers.IntegerField()

def get_download_url(self, obj):
"""
Get the representation for SerializerMethodField `downloadUrl`
"""
return urljoin(settings.LMS_ROOT_URL, obj.get("download_url"))


class ResponseSerializer(serializers.Serializer):
"""Serializer for the responseData api construct, which represents the contents of a submitted learner response"""
Expand Down
Loading

0 comments on commit 522d2a3

Please sign in to comment.