Skip to content

Commit

Permalink
refactor: [AXIMST-791] change navigation sidebar API url and refactor…
Browse files Browse the repository at this point in the history
… after external review
  • Loading branch information
NiedielnitsevIvan committed Apr 11, 2024
1 parent 7a88d0f commit bec38d1
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 112 deletions.
2 changes: 0 additions & 2 deletions cms/djangoapps/contentstore/signals/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
CoursewareSearchIndexer,
LibrarySearchIndexer,
)
from cms.djangoapps.contentstore.utils import drop_course_sidebar_blocks_cache
from common.djangoapps.track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type
from common.djangoapps.util.block_utils import yield_dynamic_block_descendants
from lms.djangoapps.grades.api import task_compute_all_grades_for_course
Expand Down Expand Up @@ -142,7 +141,6 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
# register special exams asynchronously after the data is ready
course_key_str = str(course_key)
transaction.on_commit(lambda: update_special_exams_and_publish.delay(course_key_str))
drop_course_sidebar_blocks_cache(course_key_str)

if key_supports_outlines(course_key):
# Push the course outline to learning_sequences asynchronously.
Expand Down
94 changes: 1 addition & 93 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
from collections import defaultdict
from contextlib import contextmanager
from datetime import datetime, timezone
from urllib.parse import quote_plus, unquote
from urllib.parse import quote_plus
from uuid import uuid4

from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils import translation
Expand Down Expand Up @@ -2235,94 +2234,3 @@ def send_course_update_notification(course_key, content, user):
audience_filters={},
)
COURSE_NOTIFICATION_REQUESTED.send_event(course_notification_data=notification_data)


def get_xblock_validation_messages(xblock):
"""
Retrieves validation messages for a given xblock.
Args:
xblock: The xblock object to validate.
Returns:
list: A list of validation error messages.
"""
validation_json = xblock.validate().to_json()
return validation_json['messages']


def get_xblock_render_error(request, xblock):
"""
Checks if there are any rendering errors for a given block and return these.
Args:
request: WSGI request object
xblock: The xblock object to rendering.
Returns:
str: Error message which happened while rendering of xblock.
"""
from cms.djangoapps.contentstore.views.preview import _load_preview_block
from xmodule.studio_editable import has_author_view
from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW

def get_xblock_render_context(request, block):
"""
Return a dict of the data needs for render of each block.
"""
can_edit = has_studio_write_access(request.user, block.usage_key.course_key)

return {
"is_unit_page": False,
"can_edit": can_edit,
"root_xblock": xblock,
"reorderable_items": set(),
"paging": None,
"force_render": None,
"item_url": "/container/{block.location}",
"tags_count_map": {},
}

try:
block = _load_preview_block(request, xblock)
preview_view = AUTHOR_VIEW if has_author_view(block) else STUDENT_VIEW
render_context = get_xblock_render_context(request, block)
block.render(preview_view, render_context)
except Exception as exc: # pylint: disable=broad-except
return str(exc)

return ""


def drop_course_sidebar_blocks_cache(course_id: str):
"""
Drop the course sidebar blocks cache for the given course.
"""
cache_key_prefix = f"course_sidebar_blocks_{course_id}"
cache_keys = get_cache_keys(cache_key_prefix)

cache.delete_many(cache_keys)


def get_cache_keys(cache_key_prefix):
"""
Get all cache keys for the given cache key prefix.
LocMemCache does not have a keys method, so we need to iterate over the cache
and manually filter out the keys that match the given prefix.
"""
cache_backend = settings.CACHES['default']['BACKEND']
if cache_backend == 'django_redis.cache.RedisCache':
yield cache.iter_keys(f"{cache_key_prefix}*")
elif cache_backend == 'django.core.cache.backends.locmem.LocMemCache':
for key in cache._cache.keys(): # pylint: disable=protected-access
try:
decoded_key = unquote(key.split(':', 2)[-1], encoding='utf-8')
except IndexError:
continue

if decoded_key.startswith(cache_key_prefix):
yield decoded_key
else:
log.error(f"Unsupported cache backend: {cache_backend}")
yield
2 changes: 2 additions & 0 deletions lms/djangoapps/course_home_api/outline/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ def get_vertical_icon_class(block):
for higher_class in icon_call_priority:
if higher_class in child_classes:
new_class = higher_class
# if 'openassessment' in child_classes:
# new_class = 'fa-pencil-square-o'
return new_class


Expand Down
27 changes: 14 additions & 13 deletions lms/djangoapps/course_home_api/outline/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ def finalize_response(self, request, response, *args, **kwargs):
return expose_header('Date', response)


class CourseSidebarBlocksView(RetrieveAPIView):
class CourseNavigationBlocksView(RetrieveAPIView):
"""
**Use Cases**
Request details for the sidebar navigation of the course.
Expand Down Expand Up @@ -416,7 +416,7 @@ class CourseSidebarBlocksView(RetrieveAPIView):

serializer_class = CourseBlockSerializer
COURSE_BLOCKS_CACHE_KEY_TEMPLATE = (
'course_sidebar_blocks_{course_key_string}_{user_id}_{user_cohort_id}'
'course_sidebar_blocks_{course_key_string}_{course_version}_{user_id}_{user_cohort_id}'
'_{allow_public}_{allow_public_outline}_{is_masquerading}'
)
COURSE_BLOCKS_CACHE_TIMEOUT = 60 * 60 # 1 hour
Expand Down Expand Up @@ -445,12 +445,13 @@ def get(self, request, *args, **kwargs):
enrollment = CourseEnrollment.get_enrollment(request.user, course_key)

try:
user_cohort = get_cohort(request.user, course_key)
user_cohort = get_cohort(request.user, course_key, use_cached=True)
except ValueError:
user_cohort = None

cache_key = self.COURSE_BLOCKS_CACHE_KEY_TEMPLATE.format(
course_key_string=course_key_string,
course_version=str(course.course_version),
user_id=request.user.id,
user_cohort_id=getattr(user_cohort, 'id', ''),
allow_public=allow_public,
Expand All @@ -471,7 +472,7 @@ def get(self, request, *args, **kwargs):
if courseware_mfe_navigation_sidebar_blocks_caching_is_enabled():
cache.set(cache_key, course_blocks, self.COURSE_BLOCKS_CACHE_TIMEOUT)

course_blocks = self.filter_unavailable_blocks(course_blocks, course_key)
course_blocks = self.filter_inaccessible_blocks(course_blocks, course_key)
if course_blocks:
course_blocks = self.mark_complete_recursive(course_blocks)

Expand All @@ -486,19 +487,19 @@ def get(self, request, *args, **kwargs):

return Response(serializer.data)

def filter_unavailable_blocks(self, course_blocks, course_key):
def filter_inaccessible_blocks(self, course_blocks, course_key):
"""
Filter out sections and subsections that are not available to the current user.
Filter out sections and subsections that are not accessible to the current user.
"""
if course_blocks:
user_course_outline = get_user_course_outline(course_key, self.request.user, datetime.now(tz=timezone.utc))
course_sections = course_blocks.get('children', [])
course_blocks['children'] = self.get_available_sections(user_course_outline, course_sections)
course_blocks['children'] = self.get_accessible_sections(user_course_outline, course_sections)

for section_data in course_sections:
section_data['children'] = self.get_available_sequences(
section_data['children'] = self.get_accessible_sequences(
user_course_outline,
section_data.get('children', ['completion'])
section_data.get('children', [])
)
accessible_sequence_ids = {str(usage_key) for usage_key in user_course_outline.accessible_sequences}
for sequence_data in section_data['children']:
Expand Down Expand Up @@ -550,19 +551,19 @@ def get_completable_children(block):
return [child for child in block.get('children', []) if child['type'] != 'discussion']

@staticmethod
def get_available_sections(user_course_outline, course_sections):
def get_accessible_sections(user_course_outline, course_sections):
"""
Filter out sections that are not available to the user.
Filter out sections that are not accessible to the user.
"""
available_section_ids = set(map(lambda section: str(section.usage_key), user_course_outline.sections))
return list(filter(
lambda section_data: section_data['id'] in available_section_ids, course_sections
))

@staticmethod
def get_available_sequences(user_course_outline, course_sequences):
def get_accessible_sequences(user_course_outline, course_sequences):
"""
Filter out sequences that are not available to the user.
Filter out sequences that are not accessible to the user.
"""
available_sequence_ids = set(map(str, user_course_outline.sequences))

Expand Down
8 changes: 4 additions & 4 deletions lms/djangoapps/course_home_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from lms.djangoapps.course_home_api.course_metadata.views import CourseHomeMetadataView
from lms.djangoapps.course_home_api.dates.views import DatesTabView
from lms.djangoapps.course_home_api.outline.views import (
CourseSidebarBlocksView,
CourseNavigationBlocksView,
OutlineTabView,
dismiss_welcome_message,
save_course_goal,
Expand Down Expand Up @@ -49,9 +49,9 @@
name='outline-tab'
),
re_path(
fr'sidebar/{settings.COURSE_KEY_PATTERN}',
CourseSidebarBlocksView.as_view(),
name='course-sidebar-blocks'
fr'navigation/{settings.COURSE_KEY_PATTERN}',
CourseNavigationBlocksView.as_view(),
name='course-navigation'
),
re_path(
r'dismiss_welcome_message',
Expand Down

0 comments on commit bec38d1

Please sign in to comment.