runtime.py 7.83 KB
Newer Older
1 2 3
"""
Module implementing `xblock.runtime.Runtime` functionality for the LMS
"""
4
import xblock.reference.plugins
5 6
from django.conf import settings
from django.core.urlresolvers import reverse
7 8 9

from badges.service import BadgingService
from badges.utils import badges_enabled
10
from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
11
from openedx.core.djangoapps.user_api.course_tag import api as user_course_tag_api
12
from openedx.core.lib.url_utils import quote_slashes
13
from openedx.core.lib.xblock_utils import xblock_local_resource_url
14
from request_cache.middleware import RequestCache
15
from xmodule.library_tools import LibraryToolsService
16
from xmodule.modulestore.django import ModuleI18nService, modulestore
17
from xmodule.partitions.partitions_service import PartitionService
18 19 20
from xmodule.services import SettingsService
from xmodule.x_module import ModuleSystem

21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
def handler_url(block, handler_name, suffix='', query='', thirdparty=False):
    """
    This method matches the signature for `xblock.runtime:Runtime.handler_url()`

    See :method:`xblock.runtime:Runtime.handler_url`
    """
    view_name = 'xblock_handler'
    if handler_name:
        # Be sure this is really a handler.
        #
        # We're checking the .__class__ instead of the block itself to avoid
        # auto-proxying from Descriptor -> Module, in case descriptors want
        # to ask for handler URLs without a student context.
        func = getattr(block.__class__, handler_name, None)
        if not func:
            raise ValueError("{!r} is not a function name".format(handler_name))

        # Is the following necessary? ProxyAttribute causes an UndefinedContext error
        # if trying this without the module system.
        #
        #if not getattr(func, "_is_xblock_handler", False):
        #    raise ValueError("{!r} is not a handler name".format(handler_name))

    if thirdparty:
        view_name = 'xblock_handler_noauth'

    url = reverse(view_name, kwargs={
        'course_id': unicode(block.location.course_key),
        'usage_id': quote_slashes(unicode(block.scope_ids.usage_id).encode('utf-8')),
        'handler': handler_name,
        'suffix': suffix,
    })

    # If suffix is an empty string, remove the trailing '/'
    if not suffix:
        url = url.rstrip('/')

    # If there is a query string, append it
    if query:
        url += '?' + query

    # If third-party, return fully-qualified url
    if thirdparty:
        scheme = "https" if settings.HTTPS == "on" else "http"
        url = '{scheme}://{host}{path}'.format(
            scheme=scheme,
            host=settings.SITE_NAME,
            path=url
        )

    return url


def local_resource_url(block, uri):
    """
    local_resource_url for Studio
    """
79
    return xblock_local_resource_url(block, uri)
80

81

82 83 84 85 86
class UserTagsService(object):
    """
    A runtime class that provides an interface to the user service.  It handles filling in
    the current course id and current user.
    """
87

88
    COURSE_SCOPE = user_course_tag_api.COURSE_SCOPE
89 90 91 92 93

    def __init__(self, runtime):
        self.runtime = runtime

    def _get_current_user(self):
94
        """Returns the real, not anonymized, current user."""
95 96 97 98 99 100 101 102 103 104
        real_user = self.runtime.get_real_user(self.runtime.anonymous_student_id)
        return real_user

    def get_tag(self, scope, key):
        """
        Get a user tag for the current course and the current user for a given key

            scope: the current scope of the runtime
            key: the key for the value we want
        """
105
        if scope != user_course_tag_api.COURSE_SCOPE:
106 107
            raise ValueError("unexpected scope {0}".format(scope))

108 109 110 111
        return user_course_tag_api.get_course_tag(
            self._get_current_user(),
            self.runtime.course_id, key
        )
112 113 114 115 116 117 118 119 120

    def set_tag(self, scope, key, value):
        """
        Set the user tag for the current course and the current user for a given key

            scope: the current scope of the runtime
            key: the key that to the value to be set
            value: the value to set
        """
121
        if scope != user_course_tag_api.COURSE_SCOPE:
122 123
            raise ValueError("unexpected scope {0}".format(scope))

124 125 126 127
        return user_course_tag_api.set_course_tag(
            self._get_current_user(),
            self.runtime.course_id, key, value
        )
128 129


130
class LmsModuleSystem(ModuleSystem):  # pylint: disable=abstract-method
131 132 133
    """
    ModuleSystem specialized to the LMS
    """
134
    def __init__(self, **kwargs):
135
        request_cache_dict = RequestCache.get_request_cache().data
136
        services = kwargs.setdefault('services', {})
137 138 139
        services['fs'] = xblock.reference.plugins.FSService()
        services['i18n'] = ModuleI18nService
        services['library_tools'] = LibraryToolsService(modulestore())
140
        services['partitions'] = PartitionService(
141
            course_id=kwargs.get('course_id'),
142
            cache=request_cache_dict
143
        )
144
        store = modulestore()
145
        services['settings'] = SettingsService()
146
        services['user_tags'] = UserTagsService(self)
147
        if badges_enabled():
148
            services['badging'] = BadgingService(course_id=kwargs.get('course_id'), modulestore=store)
149
        self.request_token = kwargs.pop('request_token', None)
150
        super(LmsModuleSystem, self).__init__(**kwargs)
151

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
    def handler_url(self, *args, **kwargs):
        """
        Implement the XBlock runtime handler_url interface.

        This is mostly just proxying to the module level `handler_url` function
        defined higher up in this file.

        We're doing this indirection because the module level `handler_url`
        logic is also needed by the `DescriptorSystem`. The particular
        `handler_url` that a `DescriptorSystem` needs will be different when
        running an LMS process or a CMS/Studio process. That's accomplished by
        monkey-patching a global. It's a long story, but please know that you
        can't just refactor and fold that logic into here without breaking
        things.

        https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes

        See :method:`xblock.runtime:Runtime.handler_url`
        """
        return handler_url(*args, **kwargs)

    def local_resource_url(self, *args, **kwargs):
        return local_resource_url(*args, **kwargs)

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    def wrap_aside(self, block, aside, view, frag, context):
        """
        Creates a div which identifies the aside, points to the original block,
        and writes out the json_init_args into a script tag.

        The default implementation creates a frag to wraps frag w/ a div identifying the xblock. If you have
        javascript, you'll need to override this impl
        """
        extra_data = {
            'block-id': quote_slashes(unicode(block.scope_ids.usage_id)),
            'url-selector': 'asideBaseUrl',
            'runtime-class': 'LmsRuntime',
        }
        if self.request_token:
            extra_data['request-token'] = self.request_token

        return self._wrap_ele(
            aside,
            view,
            frag,
            extra_data,
        )

199
    def applicable_aside_types(self, block):
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
        """
        Return all of the asides which might be decorating this `block`.

        Arguments:
            block (:class:`.XBlock`): The block to render retrieve asides for.
        """

        config = XBlockAsidesConfig.current()

        if not config.enabled:
            return []

        if block.scope_ids.block_type in config.disabled_blocks.split():
            return []

215 216 217 218 219 220 221
        # TODO: aside_type != 'acid_aside' check should be removed once AcidBlock is only installed during tests
        # (see https://openedx.atlassian.net/browse/TE-811)
        return [
            aside_type
            for aside_type in super(LmsModuleSystem, self).applicable_aside_types(block)
            if aside_type != 'acid_aside'
        ]