serializers.py 8.69 KB
Newer Older
1 2 3
"""
Serializer for video outline
"""
4 5
from rest_framework.reverse import reverse

6
from xmodule.modulestore.mongo.base import BLOCK_TYPES_WITH_CHILDREN
7
from xmodule.modulestore.django import modulestore
8
from courseware.access import has_access
9
from courseware.courses import get_course_by_id
10 11 12
from courseware.model_data import FieldDataCache
from courseware.module_render import get_module_for_descriptor
from util.module_utils import get_dynamic_descriptor_children
13 14

from edxval.api import (
15
    get_video_info_for_course_and_profiles, ValInternalError
16 17 18 19
)


class BlockOutline(object):
20 21 22
    """
    Serializes course videos, pulling data from VAL and the video modules.
    """
23
    def __init__(self, course_id, start_block, block_types, request, video_profiles):
24 25
        """Create a BlockOutline using `start_block` as a starting point."""
        self.start_block = start_block
26
        self.block_types = block_types
27
        self.course_id = course_id
28
        self.request = request  # needed for making full URLS
29 30
        self.local_cache = {}
        try:
31 32
            self.local_cache['course_videos'] = get_video_info_for_course_and_profiles(
                unicode(course_id), video_profiles
33
            )
34
        except ValInternalError:  # pragma: nocover
35 36 37
            self.local_cache['course_videos'] = {}

    def __iter__(self):
38
        def parent_or_requested_block_type(usage_key):
christopher lee committed
39
            """
40
            Returns whether the usage_key's block_type is one of self.block_types or a parent type.
christopher lee committed
41
            """
42 43 44 45
            return (
                usage_key.block_type in self.block_types or
                usage_key.block_type in BLOCK_TYPES_WITH_CHILDREN
            )
46

47 48 49 50 51
        def create_module(descriptor):
            """
            Factory method for creating and binding a module for the given descriptor.
            """
            field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
52
                self.course_id, self.request.user, descriptor, depth=0,
53
            )
54
            course = get_course_by_id(self.course_id)
55
            return get_module_for_descriptor(
56
                self.request.user, self.request, descriptor, field_data_cache, self.course_id, course=course
57 58
            )

59 60 61 62 63
        with modulestore().bulk_operations(self.course_id):
            child_to_parent = {}
            stack = [self.start_block]
            while stack:
                curr_block = stack.pop()
64

65 66 67 68 69 70 71
                if curr_block.hide_from_toc:
                    # For now, if the 'hide_from_toc' setting is set on the block, do not traverse down
                    # the hierarchy.  The reason being is that these blocks may not have human-readable names
                    # to display on the mobile clients.
                    # Eventually, we'll need to figure out how we want these blocks to be displayed on the
                    # mobile clients.  As they are still accessible in the browser, just not navigatable
                    # from the table-of-contents.
72 73
                    continue

74 75 76 77 78 79 80
                if curr_block.location.block_type in self.block_types:
                    if not has_access(self.request.user, 'load', curr_block, course_key=self.course_id):
                        continue

                    summary_fn = self.block_types[curr_block.category]
                    block_path = list(path(curr_block, child_to_parent, self.start_block))
                    unit_url, section_url = find_urls(self.course_id, curr_block, child_to_parent, self.request)
81

82 83 84 85 86 87 88
                    yield {
                        "path": block_path,
                        "named_path": [b["name"] for b in block_path],
                        "unit_url": unit_url,
                        "section_url": section_url,
                        "summary": summary_fn(self.course_id, curr_block, self.request, self.local_cache)
                    }
89

90 91 92 93 94 95 96 97 98 99
                if curr_block.has_children:
                    children = get_dynamic_descriptor_children(
                        curr_block,
                        self.request.user.id,
                        create_module,
                        usage_key_filter=parent_or_requested_block_type
                    )
                    for block in reversed(children):
                        stack.append(block)
                        child_to_parent[block] = curr_block
100 101


102 103 104 105 106 107 108 109
def path(block, child_to_parent, start_block):
    """path for block"""
    block_path = []
    while block in child_to_parent:
        block = child_to_parent[block]
        if block is not start_block:
            block_path.append({
                # to be consistent with other edx-platform clients, return the defaulted display name
110
                'name': block.display_name_with_default_escaped,
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
                'category': block.category,
                'id': unicode(block.location)
            })
    return reversed(block_path)


def find_urls(course_id, block, child_to_parent, request):
    """
    Find the section and unit urls for a block.

    Returns:
        unit_url, section_url:
            unit_url (str): The url of a unit
            section_url (str): The url of a section

    """
    block_path = []
    while block in child_to_parent:
        block = child_to_parent[block]
        block_path.append(block)

    block_list = list(reversed(block_path))
    block_count = len(block_list)

    chapter_id = block_list[1].location.block_id if block_count > 1 else None
    section = block_list[2] if block_count > 2 else None
    position = None

    if block_count > 3:
        position = 1
        for block in section.children:
            if block.name == block_list[3].url_name:
                break
            position += 1

    kwargs = {'course_id': unicode(course_id)}
    if chapter_id is None:
148 149
        course_url = reverse("courseware", kwargs=kwargs, request=request)
        return course_url, course_url
150 151 152

    kwargs['chapter'] = chapter_id
    if section is None:
153 154
        chapter_url = reverse("courseware_chapter", kwargs=kwargs, request=request)
        return chapter_url, chapter_url
155 156

    kwargs['section'] = section.url_name
157
    section_url = reverse("courseware_section", kwargs=kwargs, request=request)
158
    if position is None:
159
        return section_url, section_url
160 161 162 163 164 165

    kwargs['position'] = position
    unit_url = reverse("courseware_position", kwargs=kwargs, request=request)
    return unit_url, section_url


166
def video_summary(video_profiles, course_id, video_descriptor, request, local_cache):
167 168 169
    """
    returns summary dict for the given video module
    """
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    always_available_data = {
        "name": video_descriptor.display_name,
        "category": video_descriptor.category,
        "id": unicode(video_descriptor.scope_ids.usage_id),
        "only_on_web": video_descriptor.only_on_web,
    }

    if video_descriptor.only_on_web:
        ret = {
            "video_url": None,
            "video_thumbnail_url": None,
            "duration": 0,
            "size": 0,
            "transcripts": {},
            "language": None,
        }
        ret.update(always_available_data)
        return ret

189 190 191 192 193 194 195 196 197 198 199 200 201 202
    # Get encoded videos
    video_data = local_cache['course_videos'].get(video_descriptor.edx_video_id, {})

    # Get highest priority video to populate backwards compatible field
    default_encoded_video = {}

    if video_data:
        for profile in video_profiles:
            default_encoded_video = video_data['profiles'].get(profile, {})
            if default_encoded_video:
                break

    if default_encoded_video:
        video_url = default_encoded_video['url']
203 204 205 206 207 208
    # Then fall back to VideoDescriptor fields for video URLs
    elif video_descriptor.html5_sources:
        video_url = video_descriptor.html5_sources[0]
    else:
        video_url = video_descriptor.source

209 210 211
    # Get duration/size, else default
    duration = video_data.get('duration', None)
    size = default_encoded_video.get('file_size', 0)
212 213

    # Transcripts...
Alexander Kryklia committed
214 215
    transcripts_info = video_descriptor.get_transcripts_info()
    transcript_langs = video_descriptor.available_translations(transcripts_info, verify_assets=False)
Dave St.Germain committed
216

217 218 219 220 221 222 223 224 225 226 227 228 229
    transcripts = {
        lang: reverse(
            'video-transcripts-detail',
            kwargs={
                'course_id': unicode(course_id),
                'block_id': video_descriptor.scope_ids.usage_id.block_id,
                'lang': lang
            },
            request=request,
        )
        for lang in transcript_langs
    }

230
    ret = {
231 232 233 234 235
        "video_url": video_url,
        "video_thumbnail_url": None,
        "duration": duration,
        "size": size,
        "transcripts": transcripts,
Alexander Kryklia committed
236
        "language": video_descriptor.get_default_transcript_language(transcripts_info),
237
        "encoded_videos": video_data.get('profiles')
238
    }
239 240
    ret.update(always_available_data)
    return ret