modulestore.py 13.1 KB
Newer Older
cewing committed
1 2 3 4 5 6 7 8 9 10 11 12
# -*- coding: utf-8 -*-
"""A modulestore wrapper

It will 'unwrap' ccx keys on the way in and re-wrap them on the way out

In practical terms this means that when an object is retrieved from modulestore
using a CCXLocator or CCXBlockUsageLocator as the key, the equivalent
CourseLocator or BlockUsageLocator will actually be used. And all objects
returned from the modulestore will have their keys updated to be the CCX
version that was passed in.
"""
from contextlib import contextmanager
13
from functools import partial
cewing committed
14 15
from ccx_keys.locator import CCXLocator, CCXBlockUsageLocator
from opaque_keys.edx.locator import CourseLocator, BlockUsageLocator
16
from xmodule.modulestore import XMODULE_FIELDS_WITH_USAGE_KEYS
cewing committed
17 18 19


def strip_ccx(val):
20 21 22 23
    """remove any reference to a CCX from the incoming value

    return a tuple of the stripped value and the id of the ccx
    """
cewing committed
24 25
    retval = val
    ccx_id = None
26 27 28
    if isinstance(retval, CCXLocator):
        ccx_id = retval.ccx
        retval = retval.to_course_locator()
29
    elif isinstance(retval, CCXBlockUsageLocator):
30 31
        ccx_id = retval.course_key.ccx
        retval = retval.to_block_locator()
32 33 34 35 36
    else:
        for field_name in XMODULE_FIELDS_WITH_USAGE_KEYS:
            if hasattr(retval, field_name):
                stripped_field_value, ccx_id = strip_ccx(getattr(retval, field_name))
                setattr(retval, field_name, stripped_field_value)
cewing committed
37 38 39 40
    return retval, ccx_id


def restore_ccx(val, ccx_id):
41 42 43 44
    """restore references to a CCX to the incoming value

    returns the value converted to a CCX-aware state, using the provided ccx_id
    """
cewing committed
45 46 47 48 49
    if isinstance(val, CourseLocator):
        return CCXLocator.from_course_locator(val, ccx_id)
    elif isinstance(val, BlockUsageLocator):
        ccx_key = restore_ccx(val.course_key, ccx_id)
        val = CCXBlockUsageLocator(ccx_key, val.block_type, val.block_id)
50 51 52
    for field_name in XMODULE_FIELDS_WITH_USAGE_KEYS:
        if hasattr(val, field_name):
            setattr(val, field_name, restore_ccx(getattr(val, field_name), ccx_id))
cewing committed
53 54 55 56 57
    if hasattr(val, 'children'):
        val.children = restore_ccx_collection(val.children, ccx_id)
    return val


58
def restore_ccx_collection(field_value, ccx_id=None):
59 60 61 62 63
    """restore references to a CCX to collections of incoming values

    returns the original collection with all values converted to a ccx-aware
    state, using the provided ccx_id
    """
64 65
    if ccx_id is None:
        return field_value
cewing committed
66 67 68 69 70 71 72 73 74 75
    if isinstance(field_value, list):
        field_value = [restore_ccx(fv, ccx_id) for fv in field_value]
    elif isinstance(field_value, dict):
        for key, val in field_value.iteritems():
            field_value[key] = restore_ccx(val, ccx_id)
    else:
        field_value = restore_ccx(field_value, ccx_id)
    return field_value


76 77
@contextmanager
def remove_ccx(to_strip):
78 79 80 81
    """A context manager for wrapping modulestore api methods.

    yields a stripped value and a function suitable for restoring it
    """
82 83 84 85
    stripped, ccx = strip_ccx(to_strip)
    yield stripped, partial(restore_ccx_collection, ccx_id=ccx)


cewing committed
86
class CCXModulestoreWrapper(object):
87 88 89 90 91 92
    """This class wraps a modulestore

    The purpose is to remove ccx-specific identifiers during lookup and restore
    it after retrieval so that data can be stored local to a course, but
    referenced in app context as ccx-specific
    """
cewing committed
93 94

    def __init__(self, modulestore):
95 96
        """wrap the provided modulestore"""
        self.__dict__['_modulestore'] = modulestore
cewing committed
97 98

    def __getattr__(self, name):
99
        """look up missing attributes on the wrapped modulestore"""
cewing committed
100 101
        return getattr(self._modulestore, name)

102 103 104 105 106 107 108 109
    def __setattr__(self, name, value):
        """set attributes only on the wrapped modulestore"""
        setattr(self._modulestore, name, value)

    def __delattr__(self, name):
        """delete attributes only on the wrapped modulestore"""
        delattr(self._modulestore, name)

cewing committed
110
    def _clean_locator_for_mapping(self, locator):
111 112 113 114 115 116
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(locator) as (locator, restore):
            # pylint: disable=protected-access
            return restore(
                self._modulestore._clean_locator_for_mapping(locator)
            )
cewing committed
117 118

    def _get_modulestore_for_courselike(self, locator=None):
119
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
120 121
        if locator is not None:
            locator, _ = strip_ccx(locator)
122
        # pylint: disable=protected-access
cewing committed
123 124 125
        return self._modulestore._get_modulestore_for_courselike(locator)

    def fill_in_run(self, course_key):
126 127 128
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.fill_in_run(course_key))
cewing committed
129 130

    def has_item(self, usage_key, **kwargs):
131 132
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        usage_key, _ = strip_ccx(usage_key)
cewing committed
133 134 135
        return self._modulestore.has_item(usage_key, **kwargs)

    def get_item(self, usage_key, depth=0, **kwargs):
136 137 138 139 140
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(usage_key) as (usage_key, restore):
            return restore(
                self._modulestore.get_item(usage_key, depth, **kwargs)
            )
cewing committed
141 142

    def get_items(self, course_key, **kwargs):
143 144 145
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.get_items(course_key, **kwargs))
cewing committed
146 147

    def get_course(self, course_key, depth=0, **kwargs):
148 149 150
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.get_course(
151
                course_key, depth=depth, **kwargs
152
            ))
cewing committed
153 154

    def has_course(self, course_id, ignore_case=False, **kwargs):
155 156 157
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_id) as (course_id, restore):
            return restore(self._modulestore.has_course(
158
                course_id, ignore_case=ignore_case, **kwargs
159
            ))
cewing committed
160 161 162 163 164

    def delete_course(self, course_key, user_id):
        """
        See xmodule.modulestore.__init__.ModuleStoreWrite.delete_course
        """
165
        course_key, _ = strip_ccx(course_key)
cewing committed
166 167 168
        return self._modulestore.delete_course(course_key, user_id)

    def get_parent_location(self, location, **kwargs):
169 170 171 172 173
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(location) as (location, restore):
            return restore(
                self._modulestore.get_parent_location(location, **kwargs)
            )
cewing committed
174 175

    def get_block_original_usage(self, usage_key):
176 177
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(usage_key) as (usage_key, restore):
178 179
            orig_key, version = self._modulestore.get_block_original_usage(usage_key)
            return restore(orig_key), version
cewing committed
180 181

    def get_modulestore_type(self, course_id):
182 183 184
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_id) as (course_id, restore):
            return restore(self._modulestore.get_modulestore_type(course_id))
cewing committed
185 186

    def get_orphans(self, course_key, **kwargs):
187 188 189
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.get_orphans(course_key, **kwargs))
cewing committed
190 191

    def clone_course(self, source_course_id, dest_course_id, user_id, fields=None, **kwargs):
192 193 194 195 196 197
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(source_course_id) as (source_course_id, _):
            with remove_ccx(dest_course_id) as (dest_course_id, dest_restore):
                return dest_restore(self._modulestore.clone_course(
                    source_course_id, dest_course_id, user_id, fields=fields, **kwargs
                ))
cewing committed
198 199

    def create_item(self, user_id, course_key, block_type, block_id=None, fields=None, **kwargs):
200 201 202
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.create_item(
203
                user_id, course_key, block_type, block_id=block_id, fields=fields, **kwargs
204
            ))
cewing committed
205 206

    def create_child(self, user_id, parent_usage_key, block_type, block_id=None, fields=None, **kwargs):
207 208 209
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(parent_usage_key) as (parent_usage_key, restore):
            return restore(self._modulestore.create_child(
210
                user_id, parent_usage_key, block_type, block_id=block_id, fields=fields, **kwargs
211
            ))
cewing committed
212 213

    def import_xblock(self, user_id, course_key, block_type, block_id, fields=None, runtime=None, **kwargs):
214 215 216
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.import_xblock(
217
                user_id, course_key, block_type, block_id, fields=fields, runtime=runtime, **kwargs
218
            ))
cewing committed
219 220

    def copy_from_template(self, source_keys, dest_key, user_id, **kwargs):
221 222 223
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(dest_key) as (dest_key, restore):
            return restore(self._modulestore.copy_from_template(
224
                source_keys, dest_key, user_id, **kwargs
225
            ))
cewing committed
226 227

    def update_item(self, xblock, user_id, allow_not_found=False, **kwargs):
228 229 230
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(xblock) as (xblock, restore):
            return restore(self._modulestore.update_item(
231
                xblock, user_id, allow_not_found=allow_not_found, **kwargs
232
            ))
cewing committed
233 234

    def delete_item(self, location, user_id, **kwargs):
235 236 237 238 239
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(location) as (location, restore):
            return restore(
                self._modulestore.delete_item(location, user_id, **kwargs)
            )
cewing committed
240 241

    def revert_to_published(self, location, user_id):
242 243 244 245 246
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(location) as (location, restore):
            return restore(
                self._modulestore.revert_to_published(location, user_id)
            )
cewing committed
247 248

    def create_xblock(self, runtime, course_key, block_type, block_id=None, fields=None, **kwargs):
249 250 251
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.create_xblock(
252
                runtime, course_key, block_type, block_id=block_id, fields=fields, **kwargs
253
            ))
cewing committed
254 255

    def has_published_version(self, xblock):
256 257 258
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(xblock) as (xblock, restore):
            return restore(self._modulestore.has_published_version(xblock))
cewing committed
259 260

    def publish(self, location, user_id, **kwargs):
261 262 263 264 265
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(location) as (location, restore):
            return restore(
                self._modulestore.publish(location, user_id, **kwargs)
            )
cewing committed
266 267

    def unpublish(self, location, user_id, **kwargs):
268 269 270 271 272
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(location) as (location, restore):
            return restore(
                self._modulestore.unpublish(location, user_id, **kwargs)
            )
cewing committed
273 274

    def convert_to_draft(self, location, user_id):
275 276 277 278 279
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(location) as (location, restore):
            return restore(
                self._modulestore.convert_to_draft(location, user_id)
            )
cewing committed
280 281

    def has_changes(self, xblock):
282 283 284
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(xblock) as (xblock, restore):
            return restore(self._modulestore.has_changes(xblock))
cewing committed
285 286

    def check_supports(self, course_key, method):
287
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
288 289 290 291 292
        course_key, _ = strip_ccx(course_key)
        return self._modulestore.check_supports(course_key, method)

    @contextmanager
    def branch_setting(self, branch_setting, course_id=None):
293
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
294 295 296 297 298 299
        course_id, _ = strip_ccx(course_id)
        with self._modulestore.branch_setting(branch_setting, course_id):
            yield

    @contextmanager
    def bulk_operations(self, course_id, emit_signals=True):
300
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
301 302 303
        course_id, _ = strip_ccx(course_id)
        with self._modulestore.bulk_operations(course_id, emit_signals=emit_signals):
            yield