modulestore.py 12.8 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 16 17 18
from ccx_keys.locator import CCXLocator, CCXBlockUsageLocator
from opaque_keys.edx.locator import CourseLocator, BlockUsageLocator


def strip_ccx(val):
19 20 21 22
    """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
23 24
    retval = val
    ccx_id = None
25 26 27
    if isinstance(retval, CCXLocator):
        ccx_id = retval.ccx
        retval = retval.to_course_locator()
28
    elif isinstance(retval, CCXBlockUsageLocator):
29 30 31
        ccx_id = retval.course_key.ccx
        retval = retval.to_block_locator()
    elif hasattr(retval, 'location'):
cewing committed
32 33 34 35 36
        retval.location, ccx_id = strip_ccx(retval.location)
    return retval, ccx_id


def restore_ccx(val, ccx_id):
37 38 39 40
    """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
41 42 43 44 45 46 47 48 49 50 51 52
    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)
    if hasattr(val, 'location'):
        val.location = restore_ccx(val.location, ccx_id)
    if hasattr(val, 'children'):
        val.children = restore_ccx_collection(val.children, ccx_id)
    return val


53
def restore_ccx_collection(field_value, ccx_id=None):
54 55 56 57 58
    """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
    """
59 60
    if ccx_id is None:
        return field_value
cewing committed
61 62 63 64 65 66 67 68 69 70
    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


71 72
@contextmanager
def remove_ccx(to_strip):
73 74 75 76
    """A context manager for wrapping modulestore api methods.

    yields a stripped value and a function suitable for restoring it
    """
77 78 79 80
    stripped, ccx = strip_ccx(to_strip)
    yield stripped, partial(restore_ccx_collection, ccx_id=ccx)


cewing committed
81
class CCXModulestoreWrapper(object):
82 83 84 85 86 87
    """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
88 89

    def __init__(self, modulestore):
90 91
        """wrap the provided modulestore"""
        self.__dict__['_modulestore'] = modulestore
cewing committed
92 93

    def __getattr__(self, name):
94
        """look up missing attributes on the wrapped modulestore"""
cewing committed
95 96
        return getattr(self._modulestore, name)

97 98 99 100 101 102 103 104
    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
105
    def _clean_locator_for_mapping(self, locator):
106 107 108 109 110 111
        """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
112 113

    def _get_modulestore_for_courselike(self, locator=None):
114
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
115 116
        if locator is not None:
            locator, _ = strip_ccx(locator)
117
        # pylint: disable=protected-access
cewing committed
118 119 120
        return self._modulestore._get_modulestore_for_courselike(locator)

    def fill_in_run(self, course_key):
121 122 123
        """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
124 125

    def has_item(self, usage_key, **kwargs):
126 127
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        usage_key, _ = strip_ccx(usage_key)
cewing committed
128 129 130
        return self._modulestore.has_item(usage_key, **kwargs)

    def get_item(self, usage_key, depth=0, **kwargs):
131 132 133 134 135
        """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
136 137

    def get_items(self, course_key, **kwargs):
138 139 140
        """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
141 142

    def get_course(self, course_key, depth=0, **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_course(
146
                course_key, depth=depth, **kwargs
147
            ))
cewing committed
148 149

    def has_course(self, course_id, ignore_case=False, **kwargs):
150 151 152
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_id) as (course_id, restore):
            return restore(self._modulestore.has_course(
153
                course_id, ignore_case=ignore_case, **kwargs
154
            ))
cewing committed
155 156 157 158 159

    def delete_course(self, course_key, user_id):
        """
        See xmodule.modulestore.__init__.ModuleStoreWrite.delete_course
        """
160
        course_key, _ = strip_ccx(course_key)
cewing committed
161 162 163
        return self._modulestore.delete_course(course_key, user_id)

    def get_parent_location(self, location, **kwargs):
164 165 166 167 168
        """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
169 170

    def get_block_original_usage(self, usage_key):
171 172
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(usage_key) as (usage_key, restore):
173 174
            orig_key, version = self._modulestore.get_block_original_usage(usage_key)
            return restore(orig_key), version
cewing committed
175 176

    def get_modulestore_type(self, course_id):
177 178 179
        """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
180 181

    def get_orphans(self, course_key, **kwargs):
182 183 184
        """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
185 186

    def clone_course(self, source_course_id, dest_course_id, user_id, fields=None, **kwargs):
187 188 189 190 191 192
        """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
193 194

    def create_item(self, user_id, course_key, block_type, block_id=None, fields=None, **kwargs):
195 196 197
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.create_item(
198
                user_id, course_key, block_type, block_id=block_id, fields=fields, **kwargs
199
            ))
cewing committed
200 201

    def create_child(self, user_id, parent_usage_key, block_type, block_id=None, fields=None, **kwargs):
202 203 204
        """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(
205
                user_id, parent_usage_key, block_type, block_id=block_id, fields=fields, **kwargs
206
            ))
cewing committed
207 208

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

    def copy_from_template(self, source_keys, dest_key, user_id, **kwargs):
216 217 218
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(dest_key) as (dest_key, restore):
            return restore(self._modulestore.copy_from_template(
219
                source_keys, dest_key, user_id, **kwargs
220
            ))
cewing committed
221 222

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

    def delete_item(self, location, user_id, **kwargs):
230 231 232 233 234
        """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
235 236

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

    def create_xblock(self, runtime, course_key, block_type, block_id=None, fields=None, **kwargs):
244 245 246
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
        with remove_ccx(course_key) as (course_key, restore):
            return restore(self._modulestore.create_xblock(
247
                runtime, course_key, block_type, block_id=block_id, fields=fields, **kwargs
248
            ))
cewing committed
249 250

    def has_published_version(self, xblock):
251 252 253
        """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
254 255

    def publish(self, location, user_id, **kwargs):
256 257 258 259 260
        """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
261 262

    def unpublish(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.unpublish(location, user_id, **kwargs)
            )
cewing committed
268 269

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

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

    def check_supports(self, course_key, method):
282
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
283 284 285 286 287
        course_key, _ = strip_ccx(course_key)
        return self._modulestore.check_supports(course_key, method)

    @contextmanager
    def branch_setting(self, branch_setting, course_id=None):
288
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
289 290 291 292 293 294
        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):
295
        """See the docs for xmodule.modulestore.mixed.MixedModuleStore"""
cewing committed
296 297 298
        course_id, _ = strip_ccx(course_id)
        with self._modulestore.bulk_operations(course_id, emit_signals=emit_signals):
            yield