Skip to content

Commit

Permalink
fix: [AXIMST-10] Redirect unit page if flag MFE enabled (#2487)
Browse files Browse the repository at this point in the history
  • Loading branch information
ruzniaievdm authored and monteri committed Jan 24, 2024
1 parent d15aca1 commit ef972dc
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 106 deletions.
143 changes: 143 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/views/vertical_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
""" API Views for unit page """

import edx_api_doc_tools as apidocs
from django.http import Http404, HttpResponseBadRequest
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from cms.djangoapps.contentstore.utils import get_container_handler_context
from cms.djangoapps.contentstore.views.component import _get_item_in_course
from cms.djangoapps.contentstore.rest_api.v1.serializers import ContainerHandlerSerializer
from openedx.core.lib.api.view_utils import view_auth_classes
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order


@view_auth_classes(is_authenticated=True)
class ContainerHandlerView(APIView):
"""
View for container xblock requests to get vertical data.
"""

def get_object(self, usage_key_string):
"""
Get an object by usage-id of the block
"""
try:
usage_key = UsageKey.from_string(usage_key_string)
except InvalidKeyError:
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
return usage_key

@apidocs.schema(
parameters=[
apidocs.string_parameter(
"usage_key_string",
apidocs.ParameterLocation.PATH,
description="Container usage key",
),
],
responses={
200: ContainerHandlerSerializer,
401: "The requester is not authenticated.",
404: "The requested locator does not exist.",
},
)
def get(self, request: Request, usage_key_string: str):
"""
Get an object containing vertical data.
**Example Request**
GET /api/contentstore/v1/container_handler/{usage_key_string}
**Response Values**
If the request is successful, an HTTP 200 "OK" response is returned.
The HTTP 200 response contains a single dict that contains keys that
are the vertical's container data.
**Example Response**
```json
{
"language_code": "zh-cn",
"action": "view",
"xblock": {
"display_name": "Labs and Demos",
"display_type": "单元",
"category": "vertical"
},
"is_unit_page": true,
"is_collapsible": false,
"position": 1,
"prev_url": "block-v1-edX%2BDemo_Course%2Btype%40vertical%2Bblock%404e592689563243c484",
"next_url": "block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40vertical%2Bblock%40vertical_aae927868e55",
"new_unit_category": "vertical",
"outline_url": "/course/course-v1:edX+DemoX+Demo_Course?format=concise",
"ancestor_xblocks": [
{
"children": [
{
"url": "/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%",
"display_name": "Introduction"
},
...
],
"title": "Example Week 2: Get Interactive",
"is_last": false
},
...
],
"component_templates": [
{
"type": "advanced",
"templates": [
{
"display_name": "批注",
"category": "annotatable",
"boilerplate_name": null,
"hinted": false,
"tab": "common",
"support_level": true
},
...
},
...
],
"xblock_info": {},
"draft_preview_link": "//preview.localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/...",
"published_preview_link": "///courses/course-v1:edX+DemoX+Demo_Course/jump_to/...",
"show_unit_tags": false,
"user_clipboard": {
"content": null,
"source_usage_key": "",
"source_context_title": "",
"source_edit_url": ""
},
"is_fullwidth_content": false,
"assets_url": "/assets/course-v1:edX+DemoX+Demo_Course/",
"unit_block_id": "d6cee45205a449369d7ef8f159b22bdf",
"subsection_location": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@graded_simulations"
}
```
"""
usage_key = self.get_object(usage_key_string)
course_key = usage_key.course_key
with modulestore().bulk_operations(course_key):
try:
course, xblock, lms_link, preview_lms_link = _get_item_in_course(request, usage_key)
except ItemNotFoundError:
return HttpResponseBadRequest()

context = get_container_handler_context(request, usage_key, course, xblock)
context.update({
'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link,
})
serializer = ContainerHandlerSerializer(context)
return Response(serializer.data)
124 changes: 124 additions & 0 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from collections import defaultdict
from contextlib import contextmanager
from datetime import datetime, timezone
from urllib.parse import quote_plus
from uuid import uuid4

from django.conf import settings
Expand Down Expand Up @@ -1786,6 +1787,129 @@ def _get_course_index_context(request, course_key, course_block):
return course_index_context


def get_container_handler_context(request, usage_key, course, xblock):
"""
Utils is used to get context for container xblock requests.
It is used for both DRF and django views.
"""

from cms.djangoapps.contentstore.views.component import (
get_component_templates,
get_unit_tags,
CONTAINER_TEMPLATES,
LIBRARY_BLOCK_TYPES,
)
from cms.djangoapps.contentstore.helpers import get_parent_xblock, is_unit
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import (
add_container_page_publishing_info,
create_xblock_info,
)
from openedx.core.djangoapps.content_staging import api as content_staging_api

component_templates = get_component_templates(course)
ancestor_xblocks = []
parent = get_parent_xblock(xblock)
action = request.GET.get('action', 'view')

is_unit_page = is_unit(xblock)
unit = xblock if is_unit_page else None

is_first = True
block = xblock

# Build the breadcrumbs and find the ``Unit`` ancestor
# if it is not the immediate parent.
while parent:

if unit is None and is_unit(block):
unit = block

# add all to nav except current xblock page
if xblock != block:
current_block = {
'title': block.display_name_with_default,
'children': parent.get_children(),
'is_last': is_first
}
is_first = False
ancestor_xblocks.append(current_block)

block = parent
parent = get_parent_xblock(parent)

ancestor_xblocks.reverse()

if unit is None:
raise ValueError("Could not determine unit page")

subsection = get_parent_xblock(unit)
if subsection is None:
raise ValueError(f"Could not determine parent subsection from unit {unit.location}")

section = get_parent_xblock(subsection)
if section is None:
raise ValueError(f"Could not determine ancestor section from unit {unit.location}")

# for the sequence navigator
prev_url, next_url = get_sibling_urls(subsection, unit.location)
# these are quoted here because they'll end up in a query string on the page,
# and quoting with mako will trigger the xss linter...
prev_url = quote_plus(prev_url) if prev_url else None
next_url = quote_plus(next_url) if next_url else None

show_unit_tags = use_tagging_taxonomy_list_page()
unit_tags = None
if show_unit_tags and is_unit_page:
unit_tags = get_unit_tags(usage_key)

# Fetch the XBlock info for use by the container page. Note that it includes information
# about the block's ancestors and siblings for use by the Unit Outline.
xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page, tags=unit_tags)

if is_unit_page:
add_container_page_publishing_info(xblock, xblock_info)

# need to figure out where this item is in the list of children as the
# preview will need this
index = 1
for child in subsection.get_children():
if child.location == unit.location:
break
index += 1

# Get the status of the user's clipboard so they can paste components if they have something to paste
user_clipboard = content_staging_api.get_user_clipboard_json(request.user.id, request)
library_block_types = [problem_type['component'] for problem_type in LIBRARY_BLOCK_TYPES]
is_library_xblock = xblock.location.block_type in library_block_types

context = {
'language_code': request.LANGUAGE_CODE,
'context_course': course, # Needed only for display of menus at top of page.
'action': action,
'xblock': xblock,
'xblock_locator': xblock.location,
'unit': unit,
'is_unit_page': is_unit_page,
'is_collapsible': is_library_xblock,
'subsection': subsection,
'section': section,
'position': index,
'prev_url': prev_url,
'next_url': next_url,
'new_unit_category': 'vertical',
'outline_url': '{url}?format=concise'.format(url=reverse_course_url('course_handler', course.id)),
'ancestor_xblocks': ancestor_xblocks,
'component_templates': component_templates,
'xblock_info': xblock_info,
'templates': CONTAINER_TEMPLATES,
'show_unit_tags': show_unit_tags,
# Status of the user's clipboard, exactly as would be returned from the "GET clipboard" REST API.
'user_clipboard': user_clipboard,
'is_fullwidth_content': is_library_xblock,
}
return context


class StudioPermissionsService:
"""
Service that can provide information about a user's permissions.
Expand Down
Loading

0 comments on commit ef972dc

Please sign in to comment.