Example #1
0
        def convert_item(item, to_be_deleted):
            """
            Convert the subtree
            """
            # collect the children's ids for future processing
            next_tier = []
            for child in item.get('definition', {}).get('children', []):
                child_loc = Location.from_deprecated_string(child)
                next_tier.append(child_loc.to_deprecated_son())

            # insert a new DRAFT version of the item
            item['_id']['revision'] = MongoRevisionKey.draft
            # ensure keys are in fixed and right order before inserting
            item['_id'] = self._id_dict_to_son(item['_id'])
            bulk_record = self._get_bulk_ops_record(location.course_key)
            bulk_record.dirty = True
            try:
                self.collection.insert(item)
            except pymongo.errors.DuplicateKeyError:
                # prevent re-creation of DRAFT versions, unless explicitly requested to ignore
                if not ignore_if_draft:
                    raise DuplicateItemError(item['_id'], self, 'collection')

            # delete the old PUBLISHED version if requested
            if delete_published:
                item['_id']['revision'] = MongoRevisionKey.published
                to_be_deleted.append(item['_id'])

            return next_tier
Example #2
0
    def update_block_location_translator(self,
                                         location,
                                         usage_id,
                                         old_course_id=None,
                                         autogenerated_usage_id=False):
        """
        Update all existing maps from location's block to the new usage_id. Used for changing the usage_id,
        thus the usage_id is required.

        Returns the usage_id. (which is primarily useful in the case of autogenerated_usage_id)

        :param location: a fully specified Location
        :param usage_id: the desired new block_id.
        :param old_course_id: the old-style org/course or org/course/run string (optional)
        :param autogenerated_usage_id: a flag used mostly for internal calls to indicate that this usage_id
        was autogenerated and thus can be overridden if it's not unique. If you set this flag, the stored
        usage_id may not be the one you submitted.
        """
        location_id = self._interpret_location_course_id(
            old_course_id, location)

        maps = self.location_map.find(location_id)
        encoded_location_name = self._encode_for_mongo(location.name)
        for map_entry in maps:
            # handle noop of renaming to same name
            if (encoded_location_name in map_entry['block_map']
                    and map_entry['block_map'][encoded_location_name].get(
                        location.category) == usage_id):
                continue
            alt_usage_id = self._verify_uniqueness(usage_id,
                                                   map_entry['block_map'])
            if alt_usage_id != usage_id:
                if autogenerated_usage_id:
                    # revise already set ones and add to remaining ones
                    usage_id = self.update_block_location_translator(
                        location, alt_usage_id, old_course_id, True)
                    return usage_id
                else:
                    raise DuplicateItemError(usage_id, self, 'location_map')

            if location.category in map_entry['block_map'].setdefault(
                    encoded_location_name, {}):
                map_entry['block_map'][encoded_location_name][
                    location.category] = usage_id
                self.location_map.update(
                    {'_id': map_entry['_id']},
                    {'$set': {
                        'block_map': map_entry['block_map']
                    }})

        return usage_id
    def clone_item(self, source, location):
        """
        Clone a new item that is a copy of the item at the location `source`
        and writes it to `location`
        """
        item = None
        try:
            source_item = self.collection.find_one(location_to_query(source))

            # allow for some programmatically generated substitutions in metadata, e.g. Discussion_id's should be auto-generated
            for key in source_item['metadata'].keys():
                if source_item['metadata'][key] == '$$GUID$$':
                    source_item['metadata'][key] = uuid4().hex

            source_item['_id'] = Location(location).dict()
            self.collection.insert(
                source_item,
                # Must include this to avoid the django debug toolbar (which defines the deprecated "safe=False")
                # from overriding our default value set in the init method.
                safe=self.collection.safe)
            item = self._load_items([source_item])[0]

            # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
            # if we add one then we need to also add it to the policy information (i.e. metadata)
            # we should remove this once we can break this reference from the course to static tabs
            if location.category == 'static_tab':
                course = self.get_course_for_item(item.location)
                existing_tabs = course.tabs or []
                existing_tabs.append({
                    'type': 'static_tab',
                    'name': item.display_name,
                    'url_slug': item.location.name
                })
                course.tabs = existing_tabs
                self.update_metadata(course.location,
                                     course._model_data._kvs._metadata)

        except pymongo.errors.DuplicateKeyError:
            raise DuplicateItemError(location)

        # recompute (and update) the metadata inheritance tree which is cached
        self.refresh_cached_metadata_inheritance_tree(Location(location))
        self.fire_updated_modulestore_signal(
            get_course_id_no_run(Location(location)), Location(location))

        return item
Example #4
0
    def convert_to_draft(self, source_location):
        """
        Create a copy of the source and mark its revision as draft.

        :param source: the location of the source (its revision must be None)
        """
        original = self.collection.find_one(location_to_query(source_location))
        draft_location = as_draft(source_location)
        if draft_location.category in DIRECT_ONLY_CATEGORIES:
            raise InvalidVersionError(source_location)
        original['_id'] = draft_location.dict()
        try:
            self.collection.insert(original)
        except pymongo.errors.DuplicateKeyError:
            raise DuplicateItemError(original['_id'])

        self.refresh_cached_metadata_inheritance_tree(draft_location)
        self.fire_updated_modulestore_signal(
            get_course_id_no_run(draft_location), draft_location)

        return self._load_items([original])[0]
Example #5
0
    def convert_to_draft(self, source_location):
        """
        Create a copy of the source and mark its revision as draft.

        :param source: the location of the source (its revision must be None)
        """
        if source_location.category in DIRECT_ONLY_CATEGORIES:
            raise InvalidVersionError(source_location)
        original = self.collection.find_one({'_id': source_location.to_deprecated_son()})
        if not original:
            raise ItemNotFoundError(source_location)
        draft_location = as_draft(source_location)
        original['_id'] = draft_location.to_deprecated_son()
        try:
            self.collection.insert(original)
        except pymongo.errors.DuplicateKeyError:
            raise DuplicateItemError(original['_id'])

        self.refresh_cached_metadata_inheritance_tree(draft_location.course_key)

        return wrap_draft(self._load_items(source_location.course_key, [original])[0])
Example #6
0
    def add_block_location_translator(self,
                                      location,
                                      old_course_id=None,
                                      usage_id=None):
        """
        Similar to translate_location which adds an entry if none is found, but this cannot create a new
        course mapping entry, only a block within such a mapping entry. If it finds no existing
        course maps, it raises ItemNotFoundError.

        In the case that there are more than one mapping record for the course identified by location, this
        method adds the mapping to all matching records! (translate_location only adds to one)

        It allows the caller to specify
        the new-style usage_id for the target rather than having the translate concoct its own.
        If the provided usage_id already exists in one of the found maps for the org/course, this function
        raises DuplicateItemError unless the old item id == the new one.

        If the caller does not provide a usage_id and there exists an entry in one of the course variants,
        it will use that entry. If more than one variant uses conflicting entries, it will raise DuplicateItemError.

        Returns the usage_id used in the mapping

        :param location: a fully specified Location
        :param old_course_id: the old-style org/course or org/course/run string (optional)
        :param usage_id: the desired new block_id. If left as None, this will generate one as per translate_location
        """
        location_id = self._interpret_location_course_id(
            old_course_id, location)

        maps = self.location_map.find(location_id)
        if maps.count() == 0:
            raise ItemNotFoundError()

        # turn maps from cursor to list
        map_list = list(maps)
        encoded_location_name = self._encode_for_mongo(location.name)
        # check whether there's already a usage_id for this location (and it agrees w/ any passed in or found)
        for map_entry in map_list:
            if (encoded_location_name in map_entry['block_map']
                    and location.category
                    in map_entry['block_map'][encoded_location_name]):
                if usage_id is None:
                    usage_id = map_entry['block_map'][encoded_location_name][
                        location.category]
                elif usage_id != map_entry['block_map'][encoded_location_name][
                        location.category]:
                    raise DuplicateItemError(usage_id, self, 'location_map')

        computed_usage_id = usage_id

        # update the maps (and generate a usage_id if it's not been set yet)
        for map_entry in map_list:
            if computed_usage_id is None:
                computed_usage_id = self._add_to_block_map(
                    location, location_id, map_entry['block_map'])
            elif (encoded_location_name not in map_entry['block_map']
                  or location.category
                  not in map_entry['block_map'][encoded_location_name]):
                alt_usage_id = self._verify_uniqueness(computed_usage_id,
                                                       map_entry['block_map'])
                if alt_usage_id != computed_usage_id:
                    if usage_id is not None:
                        raise DuplicateItemError(usage_id, self,
                                                 'location_map')
                    else:
                        # revise already set ones and add to remaining ones
                        computed_usage_id = self.update_block_location_translator(
                            location, alt_usage_id, old_course_id, True)

                map_entry['block_map'].setdefault(
                    encoded_location_name,
                    {})[location.category] = computed_usage_id
                self.location_map.update(
                    {'_id': map_entry['_id']},
                    {'$set': {
                        'block_map': map_entry['block_map']
                    }})

        return computed_usage_id