예제 #1
0
    def create_course(self,
                      org,
                      offering,
                      user_id=None,
                      fields=None,
                      **kwargs):
        """
        Creates and returns the course.

        Args:
            org (str): the organization that owns the course
            offering (str): the name of the course offering
            user_id: id of the user creating the course
            fields (dict): Fields to set on the course at initialization
            kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation

        Returns: a CourseDescriptor

        Raises:
            InvalidLocationError: If a course with the same org and offering already exists
        """

        course, _, run = offering.partition('/')
        course_id = SlashSeparatedCourseKey(org, course, run)

        # Check if a course with this org/course has been defined before (case-insensitive)
        course_search_location = SON([
            ('_id.tag', 'i4x'),
            ('_id.org', re.compile(u'^{}$'.format(course_id.org),
                                   re.IGNORECASE)),
            ('_id.course',
             re.compile(u'^{}$'.format(course_id.course), re.IGNORECASE)),
            ('_id.category', 'course'),
        ])
        courses = self.collection.find(course_search_location, fields=('_id'))
        if courses.count() > 0:
            raise InvalidLocationError(
                "There are already courses with the given org and course id: {}"
                .format([course['_id'] for course in courses]))

        location = course_id.make_usage_key('course', course_id.run)
        course = self.create_and_save_xmodule(location,
                                              fields=fields,
                                              **kwargs)

        # clone a default 'about' overview module as well
        about_location = location.replace(category='about', name='overview')
        overview_template = AboutDescriptor.get_template('overview.yaml')
        self.create_and_save_xmodule(
            about_location,
            system=course.system,
            definition_data=overview_template.get('data'))

        return course
예제 #2
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
예제 #3
0
def create_new_course(request):
    """
    Create a new course.

    Returns the URL for the course overview page.
    """
    if not auth.has_access(request.user, CourseCreatorRole()):
        raise PermissionDenied()

    org = request.json.get('org')
    number = request.json.get('number')
    display_name = request.json.get('display_name')
    run = request.json.get('run')

    # allow/disable unicode characters in course_id according to settings
    if not settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID'):
        if _has_non_ascii_characters(org) or _has_non_ascii_characters(
                number) or _has_non_ascii_characters(run):
            return JsonResponse(
                {
                    'error':
                    _('Special characters not allowed in organization, course number, and course run.'
                      )
                },
                status=400)

    try:
        course_key = SlashSeparatedCourseKey(org, number, run)

        # instantiate the CourseDescriptor and then persist it
        # note: no system to pass
        if display_name is None:
            metadata = {}
        else:
            metadata = {'display_name': display_name}

        # Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for
        # existing xml courses this cannot be changed in CourseDescriptor.
        # # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile
        # w/ xmodule.course_module.CourseDescriptor.__init__
        wiki_slug = u"{0}.{1}.{2}".format(course_key.org, course_key.course,
                                          course_key.run)
        definition_data = {'wiki_slug': wiki_slug}

        # Create the course then fetch it from the modulestore
        # Check if role permissions group for a course named like this already exists
        # Important because role groups are case insensitive
        if CourseRole.course_group_already_exists(course_key):
            raise InvalidLocationError()

        fields = {}
        fields.update(definition_data)
        fields.update(metadata)

        # Creating the course raises InvalidLocationError if an existing course with this org/name is found
        new_course = modulestore('direct').create_course(
            course_key.org,
            course_key.offering,
            fields=fields,
        )

        # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course
        # however, we can assume that b/c this user had authority to create the course, the user can add themselves
        CourseInstructorRole(new_course.id).add_users(request.user)
        auth.add_users(request.user, CourseStaffRole(new_course.id),
                       request.user)

        # seed the forums
        seed_permissions_roles(new_course.id)

        # auto-enroll the course creator in the course so that "View Live" will
        # work.
        CourseEnrollment.enroll(request.user, new_course.id)
        _users_assign_default_role(new_course.id)

        return JsonResponse(
            {'url': reverse_course_url('course_handler', new_course.id)})

    except InvalidLocationError:
        return JsonResponse({
            'ErrMsg':
            _('There is already a course defined with the same '
              'organization, course number, and course run. Please '
              'change either organization or course number to be unique.'),
            'OrgErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
            'CourseErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
        })
    except InvalidKeyError as error:
        return JsonResponse({
            "ErrMsg":
            _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)
        })
예제 #4
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)
예제 #5
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
예제 #6
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