Exemplo n.º 1
0
 def _find_one(self, location):
     '''Look for a given location in the collection.  If revision is not
     specified, returns the latest.  If the item is not present, raise
     ItemNotFoundError.
     '''
     item = self.collection.find_one(
         location_to_query(location, wildcard=False),
         sort=[('revision', pymongo.ASCENDING)],
     )
     if item is None:
         raise ItemNotFoundError(location)
     return item
Exemplo n.º 2
0
def _accessible_courses_list_from_groups(request):
    """
    List all courses available to the logged in user by reversing access group names
    """
    courses_list = []
    course_ids = set()

    user_staff_group_names = request.user.groups.filter(
        Q(name__startswith='instructor_')
        | Q(name__startswith='staff_')).values_list('name', flat=True)

    # we can only get course_ids from role names with the new format (instructor_org/number/run or
    # instructor_org.number.run but not instructor_number).
    for user_staff_group_name in user_staff_group_names:
        # to avoid duplication try to convert all course_id's to format with dots e.g. "edx.course.run"
        if user_staff_group_name.startswith("instructor_"):
            # strip starting text "instructor_"
            course_id = user_staff_group_name[11:]
        else:
            # strip starting text "staff_"
            course_id = user_staff_group_name[6:]

        course_ids.add(course_id.replace('/', '.').lower())

    for course_id in course_ids:
        # get course_location with lowercase id
        course_location = loc_mapper().translate_locator_to_location(
            CourseLocator(package_id=course_id),
            get_course=True,
            lower_only=True)
        if course_location is None:
            raise ItemNotFoundError(course_id)

        course = modulestore('direct').get_course(course_location.course_id)
        if course is None:
            raise ItemNotFoundError(course_id)

        courses_list.append(course)

    return courses_list
Exemplo n.º 3
0
 def _find_one(self, location):
     '''Look for a given location in the collection.  If revision is not
     specified, returns the latest.  If the item is not present, raise
     ItemNotFoundError.
     '''
     assert isinstance(location, Location)
     item = self.collection.find_one(
         {'_id': location.to_deprecated_son()},
         sort=[('revision', pymongo.ASCENDING)],
     )
     if item is None:
         raise ItemNotFoundError(location)
     return item
Exemplo n.º 4
0
    def clone_course(self, source_course_id, dest_course_id, user_id, fields=None, **kwargs):
        """
        Only called if cloning within this store or if env doesn't set up mixed.
        * copy the courseware
        """
        # check to see if the source course is actually there
        if not self.has_course(source_course_id):
            raise ItemNotFoundError("Cannot find a course at {0}. Aborting".format(source_course_id))

        with self.bulk_operations(dest_course_id):
            # verify that the dest_location really is an empty course
            # b/c we don't want the payload, I'm copying the guts of get_items here
            query = self._course_key_to_son(dest_course_id)
            query['_id.category'] = {'$nin': ['course', 'about']}
            if self.collection.find(query).limit(1).count() > 0:
                raise DuplicateCourseError(
                    dest_course_id,
                    "Course at destination {0} is not an empty course. "
                    "You can only clone into an empty course. Aborting...".format(
                        dest_course_id
                    )
                )

            # clone the assets
            super(DraftModuleStore, self).clone_course(source_course_id, dest_course_id, user_id, fields)

            # get the whole old course
            new_course = self.get_course(dest_course_id)
            if new_course is None:
                # create_course creates the about overview
                new_course = self.create_course(
                    dest_course_id.org, dest_course_id.course, dest_course_id.run, user_id, fields=fields
                )
            else:
                # update fields on existing course
                for key, value in fields.iteritems():
                    setattr(new_course, key, value)
                self.update_item(new_course, user_id)

            # Get all modules under this namespace which is (tag, org, course) tuple
            modules = self.get_items(source_course_id, revision=ModuleStoreEnum.RevisionOption.published_only)
            self._clone_modules(modules, dest_course_id, user_id)
            course_location = dest_course_id.make_usage_key('course', dest_course_id.run)
            self.publish(course_location, user_id)

            modules = self.get_items(source_course_id, revision=ModuleStoreEnum.RevisionOption.draft_only)
            self._clone_modules(modules, dest_course_id, user_id)

            return True
Exemplo n.º 5
0
def all_permissions_for_user_in_course(user, course_id):  # pylint: disable=invalid-name
    """Returns all the permissions the user has in the given course."""
    course = modulestore().get_course(course_id)
    if course is None:
        raise ItemNotFoundError(course_id)

    all_roles = {role.name for role in Role.objects.filter(users=user, course_id=course_id)}

    permissions = {
        permission.name
        for permission
        in Permission.objects.filter(roles__users=user, roles__course_id=course_id)
        if not permission_blacked_out(course, all_roles, permission.name)
    }
    return permissions
    def get_module_data(self, block_key, course_key):
        """
        Get block from module_data adding it to module_data if it's not already there but is in the structure

        Raises:
            ItemNotFoundError if block is not in the structure
        """
        json_data = self.module_data.get(block_key)
        if json_data is None:
            # deeper than initial descendant fetch or doesn't exist
            self.modulestore.cache_items(self, [block_key], course_key, lazy=self.lazy)
            json_data = self.module_data.get(block_key)
            if json_data is None:
                raise ItemNotFoundError(block_key)

        return json_data
Exemplo n.º 7
0
    def _update_single_item(self, location, update):
        """
        Set update on the specified item, and raises ItemNotFoundError
        if the location doesn't exist
        """

        # See http://www.mongodb.org/display/DOCS/Updating for
        # atomic update syntax
        result = self.collection.update(
            {'_id': Location(location).dict()},
            {'$set': update},
            multi=False,
            upsert=True,
            # 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)
        if result['n'] == 0:
            raise ItemNotFoundError(location)
Exemplo n.º 8
0
    def translate_location_to_course_locator(self,
                                             old_style_course_id,
                                             location,
                                             published=True,
                                             lower_only=False):
        """
        Used when you only need the CourseLocator and not a full BlockUsageLocator. Probably only
        useful for get_items which wildcards name or category.

        :param course_id: old style course id
        """
        cached = self._get_course_locator_from_cache(old_style_course_id,
                                                     published)
        if cached:
            return cached

        location_id = self._interpret_location_course_id(
            old_style_course_id, location, lower_only)

        maps = self.location_map.find(location_id)
        maps = list(maps)
        if len(maps) == 0:
            raise ItemNotFoundError(location)
        elif len(maps) == 1:
            entry = maps[0]
        else:
            # find entry w/o name, if any; otherwise, pick arbitrary
            entry = maps[0]
            for item in maps:
                if 'name' not in item['_id']:
                    entry = item
                    break
        published_course_locator = CourseLocator(package_id=entry['course_id'],
                                                 branch=entry['prod_branch'])
        draft_course_locator = CourseLocator(package_id=entry['course_id'],
                                             branch=entry['draft_branch'])
        self._cache_course_locator(old_style_course_id,
                                   published_course_locator,
                                   draft_course_locator)
        if published:
            return published_course_locator
        else:
            return draft_course_locator
Exemplo n.º 9
0
    def test_get_due_dates_error_fetching_block(self):
        request = mock.Mock()

        mock_path = 'lms.djangoapps.course_api.api.'
        with mock.patch(mock_path + 'get_dates_for_course') as mock_get_dates:
            with mock.patch(mock_path + 'modulestore') as mock_modulestore:
                mock_modulestore.return_value.get_item.side_effect = ItemNotFoundError('whatever')
                mock_get_dates.return_value = {
                    (self.section_1.location, 'due'): self.section_1.due.strftime('%Y-%m-%dT%H:%M:%SZ'),
                    (self.section_1.location, 'start'): self.section_1.start.strftime('%Y-%m-%dT%H:%M:%SZ'),
                }

                expected_due_dates = [
                    {
                        'name': UNKNOWN_BLOCK_DISPLAY_NAME,
                        'url': request.build_absolute_uri.return_value,
                        'date': self.tomorrow.strftime('%Y-%m-%dT%H:%M:%SZ'),
                    },
                ]
                actual_due_dates = get_due_dates(request, self.course.id, self.staff_user)
                assert expected_due_dates == actual_due_dates
Exemplo n.º 10
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])
Exemplo n.º 11
0
def _accessible_courses_list_from_groups(request):
    """
    List all courses available to the logged in user by reversing access group names
    """
    courses_list = {}

    instructor_courses = UserBasedRole(
        request.user, CourseInstructorRole.ROLE).courses_with_role()
    staff_courses = UserBasedRole(request.user,
                                  CourseStaffRole.ROLE).courses_with_role()
    all_courses = instructor_courses | staff_courses

    for course_access in all_courses:
        course_key = course_access.course_id
        if course_key not in courses_list:
            course = modulestore('direct').get_course(course_key)
            if course is None:
                raise ItemNotFoundError(course_key)
            courses_list[course_key] = course

    return courses_list.values()
Exemplo n.º 12
0
def all_permissions_for_user_in_course(user, course_id):  # pylint: disable=invalid-name
    """Returns all the permissions the user has in the given course."""
    if not user.is_authenticated:
        return {}

    course = modulestore().get_course(course_id)
    if course is None:
        raise ItemNotFoundError(course_id)

    roles = Role.objects.filter(users=user, course_id=course_id)
    role_names = {role.name for role in roles}

    permission_names = set()
    for role in roles:
        # Intentional n+1 query pattern to get permissions for each role because
        # Aurora's query optimizer can't handle the join proplerly on 30M+ row
        # tables (EDUCATOR-3374). Fortunately, there are very few forum roles.
        for permission in role.permissions.all():
            if not permission_blacked_out(course, role_names, permission.name):
                permission_names.add(permission.name)

    return permission_names
Exemplo n.º 13
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)
        if not original:
            raise ItemNotFoundError(source_location)
        original['_id'] = namedtuple_to_son(draft_location)
        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]
Exemplo n.º 14
0
    def validate_course_key(self, course_key, branch=ModuleStoreEnum.BranchName.draft):
        """
        Validates the course_key that would be used by maintenance app views.

        Arguments:
            course_key (string): a course key
            branch: a course locator branch, default value is ModuleStoreEnum.BranchName.draft .
                    values can be either ModuleStoreEnum.BranchName.draft or ModuleStoreEnum.BranchName.published.

        Returns:
            course_usage_key (CourseLocator): course usage locator
        """
        if not course_key:
            raise ValidationError(COURSE_KEY_ERROR_MESSAGES['empty_course_key'])

        course_usage_key = CourseKey.from_string(course_key)

        if not modulestore().has_course(course_usage_key):
            raise ItemNotFoundError(COURSE_KEY_ERROR_MESSAGES['course_key_not_found'])

        # get branch specific locator
        course_usage_key = course_usage_key.for_branch(branch)

        return course_usage_key
Exemplo n.º 15
0
    def translate_location(self,
                           old_style_course_id,
                           location,
                           published=True,
                           add_entry_if_missing=True,
                           passed_block_id=None):
        """
        Translate the given module location to a Locator. If the mapping has the run id in it, then you
        should provide old_style_course_id with that run id in it to disambiguate the mapping if there exists more
        than one entry in the mapping table for the org.course.

        The rationale for auto adding entries was that there should be a reasonable default translation
        if the code just trips into this w/o creating translations. The downfall is that ambiguous course
        locations may generate conflicting block_ids.

        Will raise ItemNotFoundError if there's no mapping and add_entry_if_missing is False.

        :param old_style_course_id: the course_id used in old mongo not the new one (optional, will use location)
        :param location:  a Location pointing to a module
        :param published: a boolean to indicate whether the caller wants the draft or published branch.
        :param add_entry_if_missing: a boolean as to whether to raise ItemNotFoundError or to create an entry if
        the course
        or block is not found in the map.
        :param passed_block_id: what block_id to assign and save if none is found
        (only if add_entry_if_missing)

        NOTE: unlike old mongo, draft branches contain the whole course; so, it applies to all category
        of locations including course.
        """
        location_id = self._interpret_location_course_id(
            old_style_course_id, location)
        if old_style_course_id is None:
            old_style_course_id = self._generate_location_course_id(
                location_id)

        cached_value = self._get_locator_from_cache(old_style_course_id,
                                                    location, published)
        if cached_value:
            return cached_value

        entry = self._get_map_entry_form_location(location_id, location,
                                                  add_entry_if_missing)
        if entry is None:
            raise ItemNotFoundError(location)

        block_id = entry['block_map'].get(
            self.encode_key_for_mongo(location.name))
        if block_id is None:
            if add_entry_if_missing:
                block_id = self._add_to_block_map(location, location_id,
                                                  entry['block_map'],
                                                  passed_block_id)
            else:
                raise ItemNotFoundError(location)
        elif isinstance(block_id, dict):
            # jump_to_id uses a None category.
            if location.category is None:
                if len(block_id) == 1:
                    # unique match (most common case)
                    block_id = block_id.values()[0]
                else:
                    raise InvalidLocationError()
            elif location.category in block_id:
                block_id = block_id[location.category]
            elif add_entry_if_missing:
                block_id = self._add_to_block_map(location, location_id,
                                                  entry['block_map'])
            else:
                raise ItemNotFoundError(location)
        else:
            raise InvalidLocationError()

        published_usage = BlockUsageLocator(package_id=entry['course_id'],
                                            branch=entry['prod_branch'],
                                            block_id=block_id)
        draft_usage = BlockUsageLocator(package_id=entry['course_id'],
                                        branch=entry['draft_branch'],
                                        block_id=block_id)
        if published:
            result = published_usage
        else:
            result = draft_usage

        self._cache_location_map_entry(old_style_course_id, location,
                                       published_usage, draft_usage)
        return result
    def migrate_mongo_course(self,
                             source_course_key,
                             user_id,
                             new_org=None,
                             new_course=None,
                             new_run=None,
                             fields=None,
                             **kwargs):
        """
        Create a new course in split_mongo representing the published and draft versions of the course from the
        original mongo store. And return the new CourseLocator

        If the new course already exists, this raises DuplicateItemError

        :param source_course_key: which course to migrate
        :param user_id: the user whose action is causing this migration
        :param new_org, new_course, new_run: (optional) identifiers for the new course. Defaults to
            the source_course_key's values.
        """
        # the only difference in data between the old and split_mongo xblocks are the locations;
        # so, any field which holds a location must change to a Locator; otherwise, the persistence
        # layer and kvs's know how to store it.
        # locations are in location, children, conditionals, course.tab

        # create the course: set fields to explicitly_set for each scope, id_root = new_course_locator, master_branch = 'production'
        original_course = self.source_modulestore.get_course(
            source_course_key, **kwargs)
        if original_course is None:
            raise ItemNotFoundError(six.text_type(source_course_key))

        if new_org is None:
            new_org = source_course_key.org
        if new_course is None:
            new_course = source_course_key.course
        if new_run is None:
            new_run = source_course_key.run

        new_course_key = CourseLocator(
            new_org,
            new_course,
            new_run,
            branch=ModuleStoreEnum.BranchName.published)
        with self.split_modulestore.bulk_operations(new_course_key):
            new_fields = self._get_fields_translate_references(
                original_course, new_course_key, None)
            if fields:
                new_fields.update(fields)
            new_course = self.split_modulestore.create_course(
                new_org,
                new_course,
                new_run,
                user_id,
                fields=new_fields,
                master_branch=ModuleStoreEnum.BranchName.published,
                skip_auto_publish=True,
                **kwargs)

            self._copy_published_modules_to_course(new_course,
                                                   original_course.location,
                                                   source_course_key, user_id,
                                                   **kwargs)

        # TODO: This should be merged back into the above transaction, but can't be until split.py
        # is refactored to have more coherent access patterns
        with self.split_modulestore.bulk_operations(new_course_key):

            # create a new version for the drafts
            self._add_draft_modules_to_course(new_course.location,
                                              source_course_key, user_id,
                                              **kwargs)

        return new_course.id
Exemplo n.º 17
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
Exemplo n.º 18
0
    def translate_location(self,
                           old_style_course_id,
                           location,
                           published=True,
                           add_entry_if_missing=True):
        """
        Translate the given module location to a Locator. If the mapping has the run id in it, then you
        should provide old_style_course_id with that run id in it to disambiguate the mapping if there exists more
        than one entry in the mapping table for the org.course.

        The rationale for auto adding entries was that there should be a reasonable default translation
        if the code just trips into this w/o creating translations. The downfall is that ambiguous course
        locations may generate conflicting block_ids.

        Will raise ItemNotFoundError if there's no mapping and add_entry_if_missing is False.

        :param old_style_course_id: the course_id used in old mongo not the new one (optional, will use location)
        :param location:  a Location pointing to a module
        :param published: a boolean to indicate whether the caller wants the draft or published branch.
        :param add_entry_if_missing: a boolean as to whether to raise ItemNotFoundError or to create an entry if
        the course
        or block is not found in the map.

        NOTE: unlike old mongo, draft branches contain the whole course; so, it applies to all category
        of locations including course.
        """
        location_id = self._interpret_location_course_id(
            old_style_course_id, location)

        maps = self.location_map.find(location_id).sort(
            '_id.name', pymongo.ASCENDING)
        if maps.count() == 0:
            if add_entry_if_missing:
                # create a new map
                course_location = location.replace(
                    category='course', name=location_id['_id.name'])
                self.create_map_entry(course_location)
                entry = self.location_map.find_one(location_id)
            else:
                raise ItemNotFoundError()
        elif maps.count() > 1:
            # if more than one, prefer the one w/o a name if that exists. Otherwise, choose the first (alphabetically)
            entry = maps[0]
        else:
            entry = maps[0]

        if published:
            branch = entry['prod_branch']
        else:
            branch = entry['draft_branch']

        usage_id = entry['block_map'].get(self._encode_for_mongo(
            location.name))
        if usage_id is None:
            if add_entry_if_missing:
                usage_id = self._add_to_block_map(location, location_id,
                                                  entry['block_map'])
            else:
                raise ItemNotFoundError(location)
        elif isinstance(usage_id, dict):
            # name is not unique, look through for the right category
            if location.category in usage_id:
                usage_id = usage_id[location.category]
            elif add_entry_if_missing:
                usage_id = self._add_to_block_map(location, location_id,
                                                  entry['block_map'])
            else:
                raise ItemNotFoundError()
        else:
            raise InvalidLocationError()

        return BlockUsageLocator(course_id=entry['course_id'],
                                 branch=branch,
                                 usage_id=usage_id)
Exemplo n.º 19
0
    def create_item(self,
                    course_or_parent_loc,
                    category,
                    user_id=None,
                    **kwargs):
        """
        Create and return the item. If parent_loc is a specific location v a course id,
        it installs the new item as a child of the parent (if the parent_loc is a specific
        xblock reference).

        :param course_or_parent_loc: Can be a course_id (org/course/run), CourseLocator,
        Location, or BlockUsageLocator but must be what the persistence modulestore expects
        """
        # find the store for the course
        course_id = self._infer_course_id_try(course_or_parent_loc)
        if course_id is None:
            raise ItemNotFoundError(u"Cannot find modulestore for %s" %
                                    course_or_parent_loc)

        store = self._get_modulestore_for_courseid(course_id)

        location = kwargs.pop('location', None)
        # invoke its create_item
        if isinstance(store, MongoModuleStore):
            block_id = kwargs.pop('block_id',
                                  getattr(location, 'name',
                                          uuid4().hex))
            # convert parent loc if it's legit
            if isinstance(course_or_parent_loc, basestring):
                parent_loc = None
                if location is None:
                    loc_dict = Location.parse_course_id(course_id)
                    loc_dict['name'] = block_id
                    location = Location(category=category, **loc_dict)
            else:
                parent_loc = course_or_parent_loc
                # must have a legitimate location, compute if appropriate
                if location is None:
                    location = parent_loc.replace(category=category,
                                                  name=block_id)
            # do the actual creation
            xblock = store.create_and_save_xmodule(location, **kwargs)
            # don't forget to attach to parent
            if parent_loc is not None and not 'detached' in xblock._class_tags:
                parent = store.get_item(parent_loc)
                parent.children.append(location.url())
                store.update_item(parent)
        elif isinstance(store, SplitMongoModuleStore):
            if isinstance(course_or_parent_loc, basestring):  # course_id
                course_or_parent_loc = loc_mapper(
                ).translate_location_to_course_locator(
                    # hardcode draft version until we figure out how we're handling branches from app
                    course_or_parent_loc,
                    None,
                    published=False)
            elif not isinstance(course_or_parent_loc, CourseLocator):
                raise ValueError(
                    u"Cannot create a child of {} in split. Wrong repr.".
                    format(course_or_parent_loc))

            # split handles all the fields in one dict not separated by scope
            fields = kwargs.get('fields', {})
            fields.update(kwargs.pop('metadata', {}))
            fields.update(kwargs.pop('definition_data', {}))
            kwargs['fields'] = fields

            xblock = store.create_item(course_or_parent_loc, category, user_id,
                                       **kwargs)
        else:
            raise NotImplementedError(u"Cannot create an item on store %s" %
                                      store)

        return xblock
Exemplo n.º 20
0
    def translate_location(self, old_style_course_id, location, published=True, add_entry_if_missing=True):
        """
        Translate the given module location to a Locator. If the mapping has the run id in it, then you
        should provide old_style_course_id with that run id in it to disambiguate the mapping if there exists more
        than one entry in the mapping table for the org.course.

        The rationale for auto adding entries was that there should be a reasonable default translation
        if the code just trips into this w/o creating translations. The downfall is that ambiguous course
        locations may generate conflicting block_ids.

        Will raise ItemNotFoundError if there's no mapping and add_entry_if_missing is False.

        :param old_style_course_id: the course_id used in old mongo not the new one (optional, will use location)
        :param location:  a Location pointing to a module
        :param published: a boolean to indicate whether the caller wants the draft or published branch.
        :param add_entry_if_missing: a boolean as to whether to raise ItemNotFoundError or to create an entry if
        the course
        or block is not found in the map.

        NOTE: unlike old mongo, draft branches contain the whole course; so, it applies to all category
        of locations including course.
        """
        location_id = self._interpret_location_course_id(old_style_course_id, location)
        if old_style_course_id is None:
            old_style_course_id = self._generate_location_course_id(location_id)

        cached_value = self._get_locator_from_cache(old_style_course_id, location, published)
        if cached_value:
            return cached_value

        maps = self.location_map.find(location_id)
        maps = list(maps)
        if len(maps) == 0:
            if add_entry_if_missing:
                # create a new map
                course_location = location.replace(category='course', name=location_id['_id']['name'])
                self.create_map_entry(course_location)
                entry = self.location_map.find_one(location_id)
            else:
                raise ItemNotFoundError()
        elif len(maps) == 1:
            entry = maps[0]
        else:
            # find entry w/o name, if any; otherwise, pick arbitrary
            entry = maps[0]
            for item in maps:
                if 'name' not in item['_id']:
                    entry = item
                    break

        block_id = entry['block_map'].get(self._encode_for_mongo(location.name))
        if block_id is None:
            if add_entry_if_missing:
                block_id = self._add_to_block_map(location, location_id, entry['block_map'])
            else:
                raise ItemNotFoundError(location)
        elif isinstance(block_id, dict):
            # name is not unique, look through for the right category
            if location.category in block_id:
                block_id = block_id[location.category]
            elif add_entry_if_missing:
                block_id = self._add_to_block_map(location, location_id, entry['block_map'])
            else:
                raise ItemNotFoundError()
        else:
            raise InvalidLocationError()

        published_usage = BlockUsageLocator(
            package_id=entry['course_id'], branch=entry['prod_branch'], block_id=block_id)
        draft_usage = BlockUsageLocator(
            package_id=entry['course_id'], branch=entry['draft_branch'], block_id=block_id)
        if published:
            result = published_usage
        else:
            result = draft_usage

        self._cache_location_map_entry(old_style_course_id, location, published_usage, draft_usage)
        return result
Exemplo n.º 21
0
    def translate_location(self,
                           location,
                           published=True,
                           add_entry_if_missing=True,
                           passed_block_id=None):
        """
        Translate the given module location to a Locator.

        The rationale for auto adding entries was that there should be a reasonable default translation
        if the code just trips into this w/o creating translations.

        Will raise ItemNotFoundError if there's no mapping and add_entry_if_missing is False.

        :param location:  a Location pointing to a module
        :param published: a boolean to indicate whether the caller wants the draft or published branch.
        :param add_entry_if_missing: a boolean as to whether to raise ItemNotFoundError or to create an entry if
        the course
        or block is not found in the map.
        :param passed_block_id: what block_id to assign and save if none is found
        (only if add_entry_if_missing)

        NOTE: unlike old mongo, draft branches contain the whole course; so, it applies to all category
        of locations including course.
        """
        course_son = self._interpret_location_course_id(location.course_key)

        cached_value = self._get_locator_from_cache(location, published)
        if cached_value:
            return cached_value

        entry = self.location_map.find_one(course_son)
        if entry is None:
            if add_entry_if_missing:
                # create a new map
                self.create_map_entry(location.course_key)
                entry = self.location_map.find_one(course_son)
            else:
                raise ItemNotFoundError(location)
        else:
            entry = self._migrate_if_necessary([entry])[0]

        block_id = entry['block_map'].get(
            self.encode_key_for_mongo(location.name))
        category = location.category
        if block_id is None:
            if add_entry_if_missing:
                block_id = self._add_to_block_map(location, course_son,
                                                  entry['block_map'],
                                                  passed_block_id)
            else:
                raise ItemNotFoundError(location)
        else:
            # jump_to_id uses a None category.
            if category is None:
                if len(block_id) == 1:
                    # unique match (most common case)
                    category = block_id.keys()[0]
                    block_id = block_id.values()[0]
                else:
                    raise InvalidLocationError()
            elif category in block_id:
                block_id = block_id[category]
            elif add_entry_if_missing:
                block_id = self._add_to_block_map(location, course_son,
                                                  entry['block_map'])
            else:
                raise ItemNotFoundError(location)

        prod_course_locator = CourseLocator(org=entry['org'],
                                            offering=entry['offering'],
                                            branch=entry['prod_branch'])
        published_usage = BlockUsageLocator(prod_course_locator,
                                            block_type=category,
                                            block_id=block_id)
        draft_usage = BlockUsageLocator(prod_course_locator.for_branch(
            entry['draft_branch']),
                                        block_type=category,
                                        block_id=block_id)
        if published:
            result = published_usage
        else:
            result = draft_usage

        self._cache_location_map_entry(location, published_usage, draft_usage)
        return result
Exemplo n.º 22
0
def register_special_exams(course_key):
    """
    This is typically called on a course published signal. The course is examined for sequences
    that are marked as timed exams. Then these are registered with the edx-proctoring
    subsystem. Likewise, if formerly registered exams are unmarked, then those
    registered exams are marked as inactive
    """
    if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
        # if feature is not enabled then do a quick exit
        return

    course = modulestore().get_course(course_key)
    if course is None:
        raise ItemNotFoundError(u"Course {} does not exist",
                                unicode(course_key))

    if not course.enable_proctored_exams and not course.enable_timed_exams:
        # likewise if course does not have these features turned on
        # then quickly exit
        return

    # get all sequences, since they can be marked as timed/proctored exams
    _timed_exams = modulestore().get_items(course_key,
                                           qualifiers={
                                               'category': 'sequential',
                                           },
                                           settings={
                                               'is_time_limited': True,
                                           })

    # filter out any potential dangling sequences
    timed_exams = [
        timed_exam for timed_exam in _timed_exams
        if is_item_in_course_tree(timed_exam)
    ]

    # enumerate over list of sequences which are time-limited and
    # add/update any exam entries in edx-proctoring
    for timed_exam in timed_exams:
        msg = (
            u'Found {location} as a timed-exam in course structure. Inspecting...'
            .format(location=unicode(timed_exam.location)))
        log.info(msg)

        exam_metadata = {
            'exam_name': timed_exam.display_name,
            'time_limit_mins': timed_exam.default_time_limit_minutes,
            'due_date': timed_exam.due,
            'is_proctored': timed_exam.is_proctored_exam,
            'is_practice_exam': timed_exam.is_practice_exam,
            'is_active': True,
            'hide_after_due': timed_exam.hide_after_due,
            'backend': course.proctoring_provider,
        }

        try:
            exam = get_exam_by_content_id(unicode(course_key),
                                          unicode(timed_exam.location))
            # update case, make sure everything is synced
            exam_metadata['exam_id'] = exam['id']

            exam_id = update_exam(**exam_metadata)
            msg = u'Updated timed exam {exam_id}'.format(exam_id=exam['id'])
            log.info(msg)

        except ProctoredExamNotFoundException:
            exam_metadata['course_id'] = unicode(course_key)
            exam_metadata['content_id'] = unicode(timed_exam.location)

            exam_id = create_exam(**exam_metadata)
            msg = u'Created new timed exam {exam_id}'.format(exam_id=exam_id)
            log.info(msg)

        exam_review_policy_metadata = {
            'exam_id': exam_id,
            'set_by_user_id': timed_exam.edited_by,
            'review_policy': timed_exam.exam_review_rules,
        }

        # only create/update exam policy for the proctored exams
        if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam:
            try:
                update_review_policy(**exam_review_policy_metadata)
            except ProctoredExamReviewPolicyNotFoundException:
                if timed_exam.exam_review_rules:  # won't save an empty rule.
                    create_exam_review_policy(**exam_review_policy_metadata)
                    msg = u'Created new exam review policy with exam_id {exam_id}'.format(
                        exam_id=exam_id)
                    log.info(msg)
        else:
            try:
                # remove any associated review policy
                remove_review_policy(exam_id=exam_id)
            except ProctoredExamReviewPolicyNotFoundException:
                pass

    # then see which exams we have in edx-proctoring that are not in
    # our current list. That means the the user has disabled it
    exams = get_all_exams_for_course(course_key)

    for exam in exams:
        if exam['is_active']:
            # try to look up the content_id in the sequences location

            search = [
                timed_exam for timed_exam in timed_exams
                if unicode(timed_exam.location) == exam['content_id']
            ]
            if not search:
                # This means it was turned off in Studio, we need to mark
                # the exam as inactive (we don't delete!)
                msg = u'Disabling timed exam {exam_id}'.format(
                    exam_id=exam['id'])
                log.info(msg)
                update_exam(
                    exam_id=exam['id'],
                    is_proctored=False,
                    is_active=False,
                )
Exemplo n.º 23
0
def validate_forus_params_values(params):
    errors = defaultdict(lambda: [])

    def mark_as_invalid(field, field_label):
        # Translators: This is for the ForUs API
        errors[field].append(
            _('Invalid {field_label} has been provided').format(
                field_label=field_label, ))

    try:
        validate_email(params.get('email'))

        try:
            user = User.objects.get(email=params.get('email'))

            if user.is_staff or user.is_superuser:
                errors['email'].append(
                    _("ForUs profile cannot be created for admins and staff."))
        except User.DoesNotExist:
            pass
    except ValidationError:
        # Translators: This is for the ForUs API
        errors['email'].append(_("The provided email format is invalid"))

    if params.get('gender') not in dict(UserProfile.GENDER_CHOICES):
        # Translators: This is for the ForUs API
        mark_as_invalid('gender', _('gender'))

    if not is_enabled_language(params.get('lang')):
        # Translators: This is for the ForUs API
        mark_as_invalid('lang', _('language'))

    if params.get('country') not in dict(countries):
        # Translators: This is for the ForUs API
        mark_as_invalid('lang', _('country'))

    if params.get('level_of_education') not in dict(
            UserProfile.LEVEL_OF_EDUCATION_CHOICES):
        # Translators: This is for the ForUs API
        mark_as_invalid('lang', _('level of education'))

    try:
        course_key = SlashSeparatedCourseKey.from_deprecated_string(
            params.get('course_id'))
        course = modulestore().get_course(course_key)

        if not course:
            raise ItemNotFoundError()

        if not course.is_self_paced():
            if not course.enrollment_has_started():

                # Translators: This is for the ForUs API
                errors['course_id'].append(
                    _('The course has not yet been opened for enrollment'))

            if course.enrollment_has_ended():
                # Translators: This is for the ForUs API
                errors['course_id'].append(
                    _('Enrollment for this course has been closed'))

    except InvalidKeyError:
        log.warning(
            u"User {username} tried to {action} with invalid course id: {course_id}"
            .format(
                username=params.get('username'),
                action=params.get('enrollment_action'),
                course_id=params.get('course_id'),
            ))

        mark_as_invalid('course_id', _('course id'))
    except ItemNotFoundError:
        # Translators: This is for the ForUs API
        errors['course_id'].append(_('The requested course does not exist'))

    try:
        if int(params['year_of_birth']) not in UserProfile.VALID_YEARS:
            # Translators: This is for the ForUs API
            mark_as_invalid('year_of_birth', _('birth year'))
    except ValueError:
        # Translators: This is for the ForUs API
        mark_as_invalid('year_of_birth', _('birth year'))

    try:
        time = datetime.strptime(params.get('time'), DATE_TIME_FORMAT)
        now = datetime.utcnow()

        if time > now:
            # Translators: This is for the ForUs API
            errors['time'].append(_('future date has been provided'))

        if time < (now - timedelta(days=1)):
            # Translators: This is for the ForUs API
            errors['time'].append(_('Request has expired'))

    except ValueError:
        # Translators: This is for the ForUs API
        mark_as_invalid('time', _('date format'))

    if len(errors):
        raise ValidationError(errors)