コード例 #1
0
ファイル: test_api.py プロジェクト: sliva/edx-platform
    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
コード例 #2
0
    def translate_location_to_course_locator(self, course_key, published=True):
        """
        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_key: a CourseKey
        :param published: a boolean representing whether or not we should return the published or draft version

        Returns a Courselocator
        """
        cached = self._get_course_locator_from_cache(course_key, published)
        if cached:
            return cached

        course_son = self._interpret_location_course_id(course_key)

        entry = self.location_map.find_one(course_son)
        if entry is None:
            raise ItemNotFoundError(course_key)

        published_course_locator = CourseLocator(org=entry['org'],
                                                 offering=entry['offering'],
                                                 branch=entry['prod_branch'])
        draft_course_locator = CourseLocator(org=entry['org'],
                                             offering=entry['offering'],
                                             branch=entry['draft_branch'])
        self._cache_course_locator(course_key, published_course_locator,
                                   draft_course_locator)
        if published:
            return published_course_locator
        else:
            return draft_course_locator
コード例 #3
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)

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

    # TODO: EDUCATOR-3374
    # The `roles__users__roles__in=all_roles` part of this filter is a hack to get the new
    # Aurora MySql query planner to properly use the unique index on the roles/users join table.
    # Without this hack, the planner uses a unique index on (role_id, user_id) to do lookup
    # by user_id only, which is a completely inefficient way to use that index.
    permission_queryset = Permission.objects.filter(
        roles__users=user,
        roles__course_id=course_id,
        roles__users__roles__in=all_roles).distinct()

    permissions = {
        permission.name
        for permission in permission_queryset
        if not permission_blacked_out(course, role_names, permission.name)
    }
    return permissions
コード例 #4
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
コード例 #5
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
コード例 #6
0
def all_permissions_for_user_in_course(user, course_id):
    """
    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)

    # Prevent a circular import
    from openedx.core.djangoapps.django_comment_common.utils import GLOBAL_STAFF_ROLE_PERMISSIONS

    if GlobalStaff().has_user(user):
        for permission in GLOBAL_STAFF_ROLE_PERMISSIONS:
            permission_names.add(permission)

    return permission_names
コード例 #7
0
ファイル: grades.py プロジェクト: skim-ks/edx-platform
    def url_and_display_name(module_state_key):
        """
        For a given module_state_key, return the problem's url and display_name.
        Handle modulestore access and caching. This method ignores permissions.
        May throw an ItemNotFoundError if there is no content that corresponds
        to this module_state_key.
        """
        problem_store = modulestore()
        if module_state_key not in state_keys_to_problem_info:
            problems = problem_store.get_items(module_state_key,
                                               course_id=course_id,
                                               depth=1)
            if not problems:
                # Likely means that the problem was deleted from the course
                # after the student had answered. We log this suspicion where
                # this exception is caught.
                raise ItemNotFoundError(
                    "Answer Distribution: Module {} not found for course {}".
                    format(module_state_key, course_id))
            problem = problems[0]
            problem_info = (problem.url_name,
                            problem.display_name_with_default)
            state_keys_to_problem_info[module_state_key] = problem_info

        return state_keys_to_problem_info[module_state_key]
コード例 #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)

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

        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
コード例 #9
0
ファイル: models.py プロジェクト: eliesmr4/myedx
    def has_permission(self, permission):
        """Returns True if this role has the given permission, False otherwise."""
        course = modulestore().get_course(self.course_id)
        if course is None:
            raise ItemNotFoundError(self.course_id)
        if permission_blacked_out(course, {self.name}, permission):
            return False

        return self.permissions.filter(name=permission).exists()
コード例 #10
0
    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
コード例 #11
0
ファイル: mixed.py プロジェクト: ybergner/edx-platform
 def delete_item(self, location, user_id=None, **kwargs):
     """
     Delete the given item from persistence. kwargs allow modulestore specific parameters.
     """
     course_id = self._infer_course_id_try(location)
     if course_id is None:
         raise ItemNotFoundError(u"Cannot find modulestore for %s" %
                                 location)
     store = self._get_modulestore_for_courseid(course_id)
     return store.delete_item(location, user_id=user_id, **kwargs)
コード例 #12
0
    def has_permission(self, permission):
        course = modulestore().get_course(self.course_id)
        if course is None:
            raise ItemNotFoundError(self.course_id)
        if self.name == FORUM_ROLE_STUDENT and \
           (permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
           (not course.forum_posts_allowed):
            return False

        return self.permissions.filter(name=permission).exists()
コード例 #13
0
ファイル: mixed.py プロジェクト: ybergner/edx-platform
 def update_item(self, xblock, user_id, allow_not_found=False):
     """
     Update the xblock persisted to be the same as the given for all types of fields
     (content, children, and metadata) attribute the change to the given user.
     """
     course_id = self._infer_course_id_try(xblock.scope_ids.usage_id)
     if course_id is None:
         raise ItemNotFoundError(u"Cannot find modulestore for %s" %
                                 xblock.scope_ids.usage_id)
     store = self._get_modulestore_for_courseid(course_id)
     return store.update_item(xblock, user_id)
コード例 #14
0
ファイル: base.py プロジェクト: pdehaye/theming-edx-platform
 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
コード例 #15
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
コード例 #16
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
コード例 #17
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
コード例 #18
0
ファイル: models.py プロジェクト: lqsang191295/Edx
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
コード例 #19
0
    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
コード例 #20
0
ファイル: base.py プロジェクト: praveen-pal/edx-platform
    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)
コード例 #21
0
ファイル: course.py プロジェクト: jianchang653/edx-platform
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()
コード例 #22
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])
コード例 #23
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
コード例 #24
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]
コード例 #25
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
コード例 #26
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
コード例 #27
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
コード例 #28
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)
コード例 #29
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)
コード例 #30
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,
                )