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
14 15 16 17

from ccx_keys.locator import CCXBlockUsageLocator, CCXLocator
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator

18
from xmodule.modulestore import XMODULE_FIELDS_WITH_USAGE_KEYS
cewing committed
19 20 21


def strip_ccx(val):
22 23 24 25
    """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
26 27
    retval = val
    ccx_id = None
28 29 30
    if isinstance(retval, CCXLocator):
        ccx_id = retval.ccx
        retval = retval.to_course_locator()
31
    elif isinstance(retval, CCXBlockUsageLocator):
32 33
        ccx_id = retval.course_key.ccx
        retval = retval.to_block_locator()
34 35 36 37 38
    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
39 40 41 42
    return retval, ccx_id


def restore_ccx(val, ccx_id):
43 44 45 46
    """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
47 48 49 50 51
    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)
52 53 54
    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
55 56 57 58 59
    if hasattr(val, 'children'):
        val.children = restore_ccx_collection(val.children, ccx_id)
    return val


60
def restore_ccx_collection(field_value, ccx_id=None):
61 62 63 64 65
    """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
    """
66 67
    if ccx_id is None:
        return field_value
cewing committed
68 69 70 71 72 73 74 75 76 77
    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


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

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


cewing committed
88
class CCXModulestoreWrapper(object):
89 90 91 92 93 94
    """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
95 96

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

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

104 105 106 107 108 109 110 111
    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
112
    def _clean_locator_for_mapping(self, locator):
113 114 115 116 117 118
        """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
119 120

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

    def fill_in_run(self, course_key):
128 129 130
        """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
131 132

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

    def get_item(self, usage_key, depth=0, **kwargs):
138 139 140 141 142
        """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
143 144

    def get_items(self, course_key, **kwargs):
145 146 147
        """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
148 149

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

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

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

    def get_parent_location(self, location, **kwargs):
171 172 173 174 175
        """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
176 177

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

    def get_modulestore_type(self, course_id):
184 185 186
        """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
187 188

    def get_orphans(self, course_key, **kwargs):
189 190 191
        """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
192 193

    def clone_course(self, source_course_id, dest_course_id, user_id, fields=None, **kwargs):
194 195 196 197 198 199
        """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
200 201

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

    def create_child(self, user_id, parent_usage_key, block_type, block_id=None, fields=None, **kwargs):
209 210 211
        """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(
212
                user_id, parent_usage_key, block_type, block_id=block_id, fields=fields, **kwargs
213
            ))
cewing committed
214 215

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

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

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

    def delete_item(self, location, user_id, **kwargs):
237 238 239 240 241
        """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
242 243

    def revert_to_published(self, location, user_id):
244 245 246 247 248
        """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
249 250

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

    def has_published_version(self, xblock):
258 259 260
        """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
261 262

    def publish(self, location, user_id, **kwargs):
263 264 265 266 267
        """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
268 269

    def unpublish(self, location, user_id, **kwargs):
270 271 272 273 274
        """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
275 276

    def convert_to_draft(self, location, user_id):
277 278 279 280 281
        """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
282 283

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

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

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

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