Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions cms/djangoapps/contentstore/rest_api/serializers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class CourseCommonSerializer(serializers.Serializer):
rerun_link = serializers.CharField()
run = serializers.CharField()
url = serializers.CharField()
translation_info = serializers.CharField()


class StrictSerializer(serializers.Serializer):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ class CourseIndexSerializer(serializers.Serializer):
rerun_notification_id = serializers.IntegerField()
advance_settings_url = serializers.CharField()
is_custom_relative_dates_active = serializers.BooleanField()
course_blocks_mapping_url = serializers.CharField()
is_translated_or_base_course = serializers.CharField()
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from rest_framework import serializers

from django.conf import settings

class CourseRerunSerializer(serializers.Serializer):
""" Serializer for course rerun """
Expand All @@ -13,3 +14,10 @@ class CourseRerunSerializer(serializers.Serializer):
number = serializers.CharField()
org = serializers.CharField()
run = serializers.CharField()
is_translated_rerun = serializers.BooleanField()
language_options = serializers.SerializerMethodField()

def get_language_options(self, obj):
"""Get language options from translated reruns."""
return settings.ALL_LANGUAGES

6 changes: 6 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/serializers/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,9 @@ class StudioHomeSerializer(serializers.Serializer):
tech_support_email = serializers.CharField()
platform_name = serializers.CharField()
user_is_active = serializers.BooleanField()
course_blocks_send_fetch_url = serializers.CharField()
show_meta_api_buttons = serializers.BooleanField()
language_options = serializers.ListSerializer(
child=serializers.ListField(child=serializers.CharField()),
allow_empty=True
)
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ class CourseSettingsSerializer(serializers.Serializer):
show_min_grade_warning = serializers.BooleanField()
sidebar_html_enabled = serializers.BooleanField()
upgrade_deadline = serializers.DateTimeField(allow_null=True)
is_destination_course = serializers.BooleanField()
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from openedx.core.djangoapps.content_tagging.toggles import is_tagging_feature_disabled

from openedx_wikilearn_features.meta_translations.utils import is_destination_block

class MessageValidation(serializers.Serializer):
"""
Expand Down Expand Up @@ -90,6 +91,7 @@ class ContainerHandlerSerializer(serializers.Serializer):
subsection_location = serializers.CharField(source="subsection.location")
course_sequence_ids = serializers.ListField(child=serializers.CharField())
library_content_picker_url = serializers.CharField()
is_translated_or_base_course = serializers.CharField()

def get_assets_url(self, obj):
"""
Expand Down Expand Up @@ -130,6 +132,13 @@ class ChildVerticalContainerSerializer(serializers.Serializer):
actions = serializers.SerializerMethodField()
validation_messages = MessageValidation(many=True)
render_error = serializers.CharField()
is_destination_block = serializers.SerializerMethodField()

def get_is_destination_block(self, obj):
"""
Method to get whether the block is a destination block.
"""
return is_destination_block(obj["xblock"].location)

def get_actions(self, obj): # pylint: disable=unused-argument
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ..serializers import CourseDetailsSerializer
from ....utils import update_course_details

from openedx_wikilearn_features.meta_translations.utils import is_destination_course

@view_auth_classes(is_authenticated=True)
class CourseDetailsView(DeveloperErrorViewMixin, APIView):
Expand Down Expand Up @@ -108,6 +109,11 @@ def get(self, request: Request, course_id: str):
# Create a mutable copy and add the field
data = dict(serializer.data)
data['topic'] = course_block.other_course_settings.get('topic')
data.update(
{
"is_destination_course": is_destination_course(course_key),
}
)
return Response(data)

@apidocs.schema(
Expand Down
2 changes: 2 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/views/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def get(self, request: Request, course_id: str):
"""
# Lazy import to avoid circular import
from openedx_wikilearn_features.wikimedia_general.utils import get_topics #pylint: disable=import-outside-toplevel
from openedx_wikilearn_features.meta_translations.utils import is_destination_course #pylint: disable=import-outside-toplevel
course_key = CourseKey.from_string(course_id)
if not has_studio_read_access(request.user, course_key):
self.permission_denied(request)
Expand All @@ -114,6 +115,7 @@ def get(self, request: Request, course_id: str):
'platform_name': settings.PLATFORM_NAME,
'licensing_enabled': settings.FEATURES.get("LICENSING", False),
'topic_options': get_topics(),
'is_destination_course': is_destination_course(course_key),
})

serializer = CourseSettingsSerializer(settings_context)
Expand Down
8 changes: 8 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v2/serializers/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from cms.djangoapps.contentstore.views.course import _get_rerun_link_for_item
from openedx.core.lib.api.serializers import CourseKeyField

from openedx_wikilearn_features.meta_translations.models import CourseTranslation
from django.utils.translation import gettext as _


class UnsucceededCourseSerializerV2(serializers.Serializer):
"""Serializer for unsucceeded course."""
Expand Down Expand Up @@ -35,6 +38,7 @@ class CourseCommonSerializerV2(serializers.Serializer):
run = serializers.CharField(source='id.run')
url = serializers.SerializerMethodField()
is_active = serializers.SerializerMethodField()
translation_info = serializers.SerializerMethodField()

def get_lms_link(self, obj):
"""Get LMS link for course."""
Expand All @@ -56,6 +60,10 @@ def get_is_active(self, obj):
"""Get whether the course is active or not."""
return not obj.has_ended()

def get_translation_info(self, obj):
"""Get whether the course is translated or base."""
return _(CourseTranslation.is_base_or_translated_course(obj.id))


class CourseHomeTabSerializerV2(serializers.Serializer):
"""Serializer for course home tab V2 with unsucceeded courses and in process course actions."""
Expand Down
3 changes: 2 additions & 1 deletion cms/djangoapps/contentstore/rest_api/v2/views/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ def get(self, request: Request):
"rerun_link": "/course_rerun/course-v1:edX+E2E-101+course",
"run": "course",
"url": "/course/course-v1:edX+E2E-101+course",
"is_active": true
"is_active": true,
"translation_info": "Translated"
},
],
"in_process_course_actions": [],
Expand Down
10 changes: 9 additions & 1 deletion cms/djangoapps/contentstore/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@
from .toggles import bypass_olx_failure_enabled
from .utils import course_import_olx_validation_is_enabled

from openedx_wikilearn_features.meta_translations.utils import rerun_course_translated

User = get_user_model()

LOGGER = get_task_logger(__name__)
Expand Down Expand Up @@ -143,6 +145,11 @@ def rerun_course(source_course_key_string, destination_course_key_string, user_i
source_course_key = CourseKey.from_string(source_course_key_string)
destination_course_key = CourseKey.from_string(destination_course_key_string)
try:
# extract translation fields
field_json = fields and json.loads(fields)
is_translated_rerun = field_json and field_json.get('is_translated_rerun', False)
language = field_json and field_json.get('language')

# deserialize the payload
fields = deserialize_fields(fields) if fields else None

Expand Down Expand Up @@ -177,9 +184,9 @@ def rerun_course(source_course_key_string, destination_course_key_string, user_i
new_restricted_course = clone_instance(restricted_course, {'course_key': destination_course_key})
for country_access_rule in country_access_rules:
clone_instance(country_access_rule, {'restricted_course': new_restricted_course})

org_data = ensure_organization(source_course_key.org)
add_organization_course(org_data, destination_course_key)
rerun_course_translated(source_course_key, destination_course_key, user_id, is_translated_rerun, language)
return "succeeded"

except DuplicateCourseError:
Expand All @@ -206,6 +213,7 @@ def rerun_course(source_course_key_string, destination_course_key_string, user_i

def deserialize_fields(json_fields):
fields = json.loads(json_fields)
fields.pop("is_translated_rerun", False)
for field_name, value in fields.items():
fields[field_name] = getattr(CourseFields, field_name).from_json(value)
return fields
Expand Down
23 changes: 22 additions & 1 deletion cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@

from .models import ComponentLink, ContainerLink

# wikimedia imports
from openedx_wikilearn_features.meta_translations.models import CourseTranslation
from openedx_wikilearn_features.meta_translations.utils import (
get_show_meta_api_buttons,
is_destination_course,
update_course_to_source,
)

IMPORTABLE_FILE_TYPES = ('.tar.gz', '.zip')
log = logging.getLogger(__name__)

Expand Down Expand Up @@ -1332,6 +1340,9 @@ def update_course_details(request, course_key, payload, course_block):
"""

from .views.entrance_exam import create_entrance_exam, delete_entrance_exam, update_entrance_exam
# check if course was destination course and now it's value is updated
if is_destination_course(course_key) and payload.get('is_destination_course') in ['false', False]:
update_course_to_source(course_key)

# if pre-requisite course feature is enabled set pre-requisite course
if is_prerequisite_courses_enabled():
Expand Down Expand Up @@ -1463,6 +1474,8 @@ def get_course_settings(request, course_key, course_block):
'enable_extended_course_details': enable_extended_course_details,
'upgrade_deadline': upgrade_deadline,
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
'is_destination_course': is_destination_course(course_key),
'is_mapped_course': bool(CourseTranslation.is_base_or_translated_course(course_key))
}
if is_prerequisite_courses_enabled():
courses, in_process_course_actions = get_courses_accessible_to_user(request)
Expand Down Expand Up @@ -1754,6 +1767,9 @@ def get_home_context(request, no_course=False):
'allowed_organizations_for_libraries': get_allowed_organizations_for_libraries(user),
'can_create_organizations': user_can_create_organizations(user),
'can_access_advanced_settings': auth.has_studio_advanced_settings_access(user),
'language_options': settings.ALL_LANGUAGES,
'course_blocks_send_fetch_url': reverse("meta_translations:course_blocks_api_send_fetch"),
'show_meta_api_buttons': get_show_meta_api_buttons(user),
}

return home_context
Expand All @@ -1772,7 +1788,9 @@ def get_course_rerun_context(course_key, course_block, user):
'display_name': course_block.display_name,
'user': user,
'course_creator_status': _get_course_creator_status(user),
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False)
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
'language_options': settings.ALL_LANGUAGES,
'is_translated_rerun': CourseTranslation.is_base_or_translated_course(course_key).upper() == "TRANSLATED"
}

return course_rerun_context
Expand Down Expand Up @@ -1957,6 +1975,8 @@ def _get_course_index_context(request, course_key, course_block):
'advance_settings_url': reverse_course_url('advanced_settings_handler', course_block.id),
'proctoring_errors': proctoring_errors,
'taxonomy_tags_widget_url': get_taxonomy_tags_widget_url(course_block.id),
'course_blocks_mapping_url': reverse("meta_translations:course_blocks_mapping"),
'is_translated_or_base_course': CourseTranslation.is_base_or_translated_course(course_key)
}

return course_index_context
Expand Down Expand Up @@ -2084,6 +2104,7 @@ def get_container_handler_context(request, usage_key, course, xblock): # pylint
'is_fullwidth_content': is_library_xblock,
'course_sequence_ids': course_sequence_ids,
'library_content_picker_url': get_library_content_picker_url(course.id),
'is_translated_or_base_course': CourseTranslation.is_base_or_translated_course(course.id),
}
return context

Expand Down
25 changes: 21 additions & 4 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@
)
from .component import ADVANCED_COMPONENT_TYPES

from openedx_wikilearn_features.meta_translations.models import CourseTranslation
from openedx_wikilearn_features.meta_translations.utils import validate_translated_rerun

log = logging.getLogger(__name__)
User = get_user_model()

Expand Down Expand Up @@ -789,7 +792,8 @@ def format_course_for_view(course):
'rerun_link': _get_rerun_link_for_item(course.id),
'org': course.display_org_with_default,
'number': course.display_number_with_default,
'run': course.location.run
'run': course.location.run,
'translation_info': _(CourseTranslation.is_base_or_translated_course(course.id)),
}
if course.id.deprecated:
course_context.update({
Expand Down Expand Up @@ -884,10 +888,20 @@ def _create_or_rerun_course(request):
{'error': _('Special characters not allowed in organization, course number, and course run.')},
status=400
)
# meta-translation feature, to identify type of rerun
language = request.json.get('language')
is_translated_rerun = request.json.get('is_translated_rerun')
source_course_key = request.json.get('source_course_key')
error_response = validate_translated_rerun(is_translated_rerun, source_course_key, language)
if error_response:
return error_response

fields = {'start': start, 'end': end}
if display_name is not None:
fields['display_name'] = display_name
if language is not None:
fields['language'] = language


# Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for
# existing xml courses this cannot be changed in CourseBlock.
Expand All @@ -897,9 +911,9 @@ def _create_or_rerun_course(request):
definition_data = {'wiki_slug': wiki_slug}
fields.update(definition_data)

source_course_key = request.json.get('source_course_key')
if source_course_key:
source_course_key = CourseKey.from_string(source_course_key)
fields['is_translated_rerun'] = is_translated_rerun
destination_course_key = rerun_course(request.user, source_course_key, org, course, run, fields)
return JsonResponse({
'url': reverse_url('course_handler'),
Expand Down Expand Up @@ -980,10 +994,13 @@ def create_new_course_in_store(store, user, org, number, run, fields):
Create course in store w/ handling instructor enrollment, permissions, and defaulting the wiki slug.
Separated out b/c command line course creation uses this as well as the web interface.
"""

# only set course language if not already set for translated courses
if 'language' not in fields:
fields.update({
'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en'),
})
# Set default language from settings and enable web certs
fields.update({
'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en'),
'cert_html_view_enabled': True,
})

Expand Down
4 changes: 3 additions & 1 deletion cms/djangoapps/contentstore/views/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
from .access import get_user_role
from .session_kv_store import SessionKeyValueStore

from openedx_wikilearn_features.meta_translations.utils import get_translation_context

__all__ = ['preview_handler']

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -341,7 +343,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'is_course': is_course,
'tags_count': tags_count,
}

template_context.update(get_translation_context(xblock))
add_webpack_js_to_fragment(frag, "js/factories/xblock_validation")

html = render_to_string('studio_xblock_wrapper.html', template_context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@
xblock_type_display_name,
)

from openedx_wikilearn_features.meta_translations.models import CourseTranslation
from openedx_wikilearn_features.meta_translations.utils import (
handle_base_course_block_deletion,
add_translation_metadata,
)
from openedx_wikilearn_features.meta_translations.wiki_components import COMPONENTS_CLASS_MAPPING

log = logging.getLogger(__name__)

CREATE_IF_NOT_FOUND = ["course_info"]
Expand Down Expand Up @@ -200,6 +207,8 @@ def handle_xblock(request, usage_key_string=None):
return HttpResponse(status=406)

elif request.method == "DELETE":
if CourseTranslation.is_base_course(str(usage_key.course_key)):
handle_base_course_block_deletion(usage_key)
_delete_item(usage_key, request.user)
return JsonResponse()
else: # Since we have a usage_key, we are updating an existing xblock.
Expand Down Expand Up @@ -282,9 +291,15 @@ def handle_xblock(request, usage_key_string=None):

def modify_xblock(usage_key, request):
request_data = request.json
xblock = get_xblock(usage_key, request.user)
# For base courses, check if content is updated then set content_updated to True in related CourseBlockData
course_id = xblock.course_id
if CourseTranslation.is_base_course(course_id) and xblock.category in COMPONENTS_CLASS_MAPPING:
COMPONENTS_CLASS_MAPPING[xblock.category]().check_and_sync_base_block_data(xblock, request.json)

return _save_xblock(
request.user,
get_xblock(usage_key, request.user),
xblock,
data=request_data.get("data"),
children_strings=request_data.get("children"),
metadata=request_data.get("metadata"),
Expand Down Expand Up @@ -1111,6 +1126,7 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
"category": xblock.category,
"has_children": xblock.has_children,
}
add_translation_metadata(xblock_info, xblock)

if course is not None and PUBLIC_VIDEO_SHARE.is_enabled(xblock.location.course_key):
xblock_info.update(
Expand Down
Loading
Loading