Beispiel #1
0
def update_collection(committer_id, collection_id, change_list,
                      commit_message):
    """Updates a collection. Commits changes.

    Args:
    - committer_id: str. The id of the user who is performing the update
        action.
    - collection_id: str. The collection id.
    - change_list: list of dicts, each representing a CollectionChange object.
        These changes are applied in sequence to produce the resulting
        collection.
    - commit_message: str or None. A description of changes made to the
        collection. For published collections, this must be present; for
        unpublished collections, it may be equal to None.
    """
    is_public = rights_manager.is_collection_public(collection_id)

    if is_public and not commit_message:
        raise ValueError(
            'Collection is public so expected a commit message but '
            'received none.')

    collection = apply_change_list(collection_id, change_list)

    _save_collection(committer_id, collection, commit_message, change_list)
    update_collection_summary(collection.id, committer_id)

    if (not rights_manager.is_collection_private(collection.id)
            and committer_id != feconf.MIGRATION_BOT_USER_ID):
        user_services.update_first_contribution_msec_if_not_set(
            committer_id, utils.get_current_time_in_millisecs())
Beispiel #2
0
    def get(self, collection_id):
        """Handles GET requests."""

        collection = collection_services.get_collection_by_id(
            collection_id, strict=False)

        if (collection is None or
                not rights_manager.Actor(self.user_id).can_view(
                    rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id)):
            self.redirect('/')
            return

        can_edit = (
            bool(self.user_id) and
            self.username not in config_domain.BANNED_USERNAMES.value and
            rights_manager.Actor(self.user_id).can_edit(
                rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id))

        self.values.update({
            'is_public': rights_manager.is_collection_public(collection_id),
            'can_edit': can_edit,
            'collection_id': collection.id,
            'title': collection.title
        })

        self.render_template('collection_editor/collection_editor.html')
Beispiel #3
0
    def get(self, collection_id):
        """Handles GET requests."""

        collection = collection_services.get_collection_by_id(collection_id,
                                                              strict=False)

        if (collection is None
                or not rights_manager.Actor(self.user_id).can_view(
                    rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id)):
            self.redirect('/')
            return

        can_edit = (
            bool(self.user_id)
            and self.username not in config_domain.BANNED_USERNAMES.value
            and rights_manager.Actor(self.user_id).can_edit(
                rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id))

        self.values.update({
            'is_public':
            rights_manager.is_collection_public(collection_id),
            'can_edit':
            can_edit,
            'collection_id':
            collection.id,
            'title':
            collection.title
        })

        self.render_template('collection_editor/collection_editor.html')
def update_collection(
        committer_id, collection_id, change_list, commit_message):
    """Update an collection. Commits changes.

    Args:
    - committer_id: str. The id of the user who is performing the update
        action.
    - collection_id: str. The collection id.
    - change_list: list of dicts, each representing a CollectionChange object.
        These changes are applied in sequence to produce the resulting
        collection.
    - commit_message: str or None. A description of changes made to the
        collection. For published collections, this must be present; for
        unpublished collections, it may be equal to None.
    """
    is_public = rights_manager.is_collection_public(collection_id)

    if is_public and not commit_message:
        raise ValueError(
            'Collection is public so expected a commit message but '
            'received none.')

    collection = apply_change_list(collection_id, change_list)
    _save_collection(committer_id, collection, commit_message, change_list)
    update_collection_summary(collection.id, committer_id)

    if not rights_manager.is_collection_private(collection.id):
        user_services.update_first_contribution_msec_if_not_set(
            committer_id, utils.get_current_time_in_millisecs())
def update_collection(committer_id, collection_id, change_list,
                      commit_message):
    """Update an collection. Commits changes.

    Args:
    - committer_id: str. The id of the user who is performing the update
        action.
    - collection_id: str. The collection id.
    - change_list: list of dicts, each representing a CollectionChange object.
        These changes are applied in sequence to produce the resulting
        collection.
    - commit_message: str or None. A description of changes made to the
        collection. For published collections, this must be present; for
        unpublished collections, it may be equal to None.
    """
    is_public = rights_manager.is_collection_public(collection_id)

    if is_public and not commit_message:
        raise ValueError(
            'Collection is public so expected a commit message but '
            'received none.')

    collection = apply_change_list(collection_id, change_list)
    _save_collection(committer_id, collection, commit_message, change_list)
    update_collection_summary(collection.id)
Beispiel #6
0
def _save_collection(committer_id, collection, commit_message, change_list):
    """Validates a collection and commits it to persistent storage. If
    successful, increments the version number of the incoming collection domain
    object by 1.

    Args:
        committer_id: str. ID of the given committer.
        collection: Collection. The collection domain object to be saved.
        commit_message: str. The commit message.
        change_list: list(dict). List of changes applied to a collection. Each
            entry in change_list is a dict that represents a CollectionChange.

    Raises:
        ValidationError: An invalid exploration was referenced in the
            collection.
        Exception: The collection model and the incoming collection domain
            object have different version numbers.
    """
    if not change_list:
        raise Exception(
            'Unexpected error: received an invalid change list when trying to '
            'save collection %s: %s' % (collection.id, change_list))

    collection_rights = rights_manager.get_collection_rights(collection.id)
    if collection_rights.status != rights_manager.ACTIVITY_STATUS_PRIVATE:
        collection.validate(strict=True)
    else:
        collection.validate(strict=False)

    # Validate that all explorations referenced by the collection exist.
    exp_ids = collection.exploration_ids
    exp_summaries = (
        exp_services.get_exploration_summaries_matching_ids(exp_ids))
    exp_summaries_dict = {
        exp_id: exp_summaries[ind]
        for (ind, exp_id) in enumerate(exp_ids)
    }
    for collection_node in collection.nodes:
        if not exp_summaries_dict[collection_node.exploration_id]:
            raise utils.ValidationError(
                'Expected collection to only reference valid explorations, '
                'but found an exploration with ID: %s (was it deleted?)' %
                collection_node.exploration_id)

    # Ensure no explorations are being added that are 'below' the public status
    # of this collection. If the collection is private, it can have both
    # private and public explorations. If it's public, it can only have public
    # explorations.
    # TODO(bhenning): Ensure the latter is enforced above when trying to
    # publish a collection.
    if rights_manager.is_collection_public(collection.id):
        validate_exps_in_collection_are_public(collection)

    collection_model = collection_models.CollectionModel.get(collection.id,
                                                             strict=False)
    if collection_model is None:
        collection_model = collection_models.CollectionModel(id=collection.id)
    else:
        if collection.version > collection_model.version:
            raise Exception(
                'Unexpected error: trying to update version %s of collection '
                'from version %s. Please reload the page and try again.' %
                (collection_model.version, collection.version))
        elif collection.version < collection_model.version:
            raise Exception(
                'Trying to update version %s of collection from version %s, '
                'which is too old. Please reload the page and try again.' %
                (collection_model.version, collection.version))

    collection_model.category = collection.category
    collection_model.title = collection.title
    collection_model.objective = collection.objective
    collection_model.language_code = collection.language_code
    collection_model.tags = collection.tags
    collection_model.schema_version = collection.schema_version
    collection_model.collection_contents = {
        'nodes':
        [collection_node.to_dict() for collection_node in collection.nodes],
        'skills': {
            skill_id: skill.to_dict()
            for skill_id, skill in collection.skills.iteritems()
        },
        'next_skill_index':
        collection.next_skill_index
    }
    collection_model.node_count = len(collection_model.nodes)
    collection_model.commit(committer_id, commit_message, change_list)
    memcache_services.delete(_get_collection_memcache_key(collection.id))
    index_collections_given_ids([collection.id])

    collection.version += 1
Beispiel #7
0
def get_learner_collection_dict_by_id(collection_id,
                                      user,
                                      strict=True,
                                      allow_invalid_explorations=False,
                                      version=None):
    """Gets a dictionary representation of a collection given by the provided
    collection ID. This dict includes user-specific playthrough information.

    Args:
        collection_id: str. The id of the collection.
        user: UserActionsInfo. Object having user_id, role and actions for
            given user.
        strict: bool. Whether to fail noisily if no collection with the given
            id exists in the datastore.
        allow_invalid_explorations: bool. Whether to also return explorations
            that are invalid, such as deleted/private explorations.
        version: str or None. The version number of the collection to be
            retrieved. If it is None, the latest version will be retrieved.

    Returns:
        dict. A dictionary that contains extra information along with the dict
        returned by collection_domain.Collection.to_dict() which includes useful
        data for the collection learner view. The information includes progress
        in the collection, information about explorations referenced within the
        collection, and a slightly nicer data structure for frontend work.

    Raises:
        ValidationError. If the collection retrieved using the given
            ID references non-existent explorations.
    """
    collection = collection_services.get_collection_by_id(collection_id,
                                                          strict=strict,
                                                          version=version)

    exp_ids = collection.exploration_ids
    exp_summary_dicts = get_displayable_exp_summary_dicts_matching_ids(
        exp_ids, user=user)
    exp_summaries_dict_map = {
        exp_summary_dict['id']: exp_summary_dict
        for exp_summary_dict in exp_summary_dicts
    }

    # TODO(bhenning): Users should not be recommended explorations they have
    # completed outside the context of a collection (see #1461).
    next_exploration_id = None
    completed_exp_ids = None
    if user.user_id:
        completed_exp_ids = (
            collection_services.get_valid_completed_exploration_ids(
                user.user_id, collection))
        next_exploration_id = collection.get_next_exploration_id(
            completed_exp_ids)
    else:
        # If the user is not logged in or they have not completed any of
        # the explorations yet within the context of this collection,
        # recommend the initial exploration.
        next_exploration_id = collection.first_exploration_id
        completed_exp_ids = []

    collection_dict = collection.to_dict()
    collection_dict['nodes'] = [node.to_dict() for node in collection.nodes]

    collection_dict['playthrough_dict'] = {
        'next_exploration_id': next_exploration_id,
        'completed_exploration_ids': completed_exp_ids
    }
    collection_dict['version'] = collection.version
    collection_is_public = rights_manager.is_collection_public(collection_id)

    # Insert an 'exploration' dict into each collection node, where the
    # dict includes meta information about the exploration (ID and title).
    for collection_node in collection_dict['nodes']:
        exploration_id = collection_node['exploration_id']
        summary_dict = exp_summaries_dict_map.get(exploration_id)
        if not allow_invalid_explorations:
            if not summary_dict:
                raise utils.ValidationError(
                    'Expected collection to only reference valid '
                    'explorations, but found an exploration with ID: %s (was '
                    'the exploration deleted or is it a private exploration '
                    'that you do not have edit access to?)' % exploration_id)
            if collection_is_public and rights_manager.is_exploration_private(
                    exploration_id):
                raise utils.ValidationError(
                    'Cannot reference a private exploration within a public '
                    'collection, exploration ID: %s' % exploration_id)

        if summary_dict:
            collection_node['exploration_summary'] = summary_dict
        else:
            collection_node['exploration_summary'] = None

    return collection_dict
Beispiel #8
0
def get_learner_collection_dict_by_id(
        collection_id, user_id, strict=True, allow_invalid_explorations=False,
        version=None):
    """Creates and returns a dictionary representation of a collection given by
    the provided collection ID. This dictionary contains extra information
    along with the dict returned by collection_domain.Collection.to_dict()
    which includes useful data for the collection learner view. The information
    includes progress in the collection, information about explorations
    referenced within the collection, and a slightly nicer data structure for
    frontend work.
    This raises a ValidationError if the collection retrieved using the given
    ID references non-existent explorations.
    which includes useful data for the collection learner view.
    """
    collection = collection_services.get_collection_by_id(
        collection_id, strict=strict, version=version)

    exp_ids = collection.exploration_ids
    exp_summary_dicts = get_displayable_exp_summary_dicts_matching_ids(
        exp_ids, editor_user_id=user_id)
    exp_summaries_dict_map = {
        exp_summary_dict['id']: exp_summary_dict
        for exp_summary_dict in exp_summary_dicts
    }

    # TODO(bhenning): Users should not be recommended explorations they have
    # completed outside the context of a collection (see #1461).
    next_exploration_ids = None
    completed_exp_ids = None
    if user_id:
        completed_exp_ids = (
            collection_services.get_valid_completed_exploration_ids(
                user_id, collection_id, collection))
        next_exploration_ids = collection.get_next_exploration_ids(
            completed_exp_ids)
    else:
        # If the user is not logged in or they have not completed any of
        # the explorations yet within the context of this collection,
        # recommend the initial explorations.
        next_exploration_ids = collection.init_exploration_ids
        completed_exp_ids = []

    collection_dict = collection.to_dict()
    collection_dict['skills'] = collection.skills
    collection_dict['playthrough_dict'] = {
        'next_exploration_ids': next_exploration_ids,
        'completed_exploration_ids': completed_exp_ids
    }
    collection_dict['version'] = collection.version
    collection_is_public = rights_manager.is_collection_public(collection_id)

    # Insert an 'exploration' dict into each collection node, where the
    # dict includes meta information about the exploration (ID and title).
    for collection_node in collection_dict['nodes']:
        exploration_id = collection_node['exploration_id']
        summary_dict = exp_summaries_dict_map.get(exploration_id)
        if not allow_invalid_explorations:
            if not summary_dict:
                raise utils.ValidationError(
                    'Expected collection to only reference valid '
                    'explorations, but found an exploration with ID: %s (was '
                    'the exploration deleted or is it a private exploration '
                    'that you do not have edit access to?)'
                    % exploration_id)
            if collection_is_public and rights_manager.is_exploration_private(
                    exploration_id):
                raise utils.ValidationError(
                    'Cannot reference a private exploration within a public '
                    'collection, exploration ID: %s' % exploration_id)

        if summary_dict:
            collection_node['exploration_summary'] = summary_dict
        else:
            collection_node['exploration_summary'] = None

    return collection_dict
Beispiel #9
0
def get_learner_collection_dict_by_id(collection_id,
                                      user_id,
                                      strict=True,
                                      allow_invalid_explorations=False,
                                      version=None):
    """Creates and returns a dictionary representation of a collection given by
    the provided collection ID. This dictionary contains extra information
    along with the dict returned by collection_domain.Collection.to_dict()
    which includes useful data for the collection learner view. The information
    includes progress in the collection, information about explorations
    referenced within the collection, and a slightly nicer data structure for
    frontend work.
    This raises a ValidationError if the collection retrieved using the given
    ID references non-existent explorations.
    which includes useful data for the collection learner view.
    """
    collection = collection_services.get_collection_by_id(collection_id,
                                                          strict=strict,
                                                          version=version)

    exp_ids = collection.exploration_ids
    exp_summary_dicts = get_displayable_exp_summary_dicts_matching_ids(
        exp_ids, editor_user_id=user_id)
    exp_summaries_dict_map = {
        exp_summary_dict['id']: exp_summary_dict
        for exp_summary_dict in exp_summary_dicts
    }

    # TODO(bhenning): Users should not be recommended explorations they have
    # completed outside the context of a collection (see #1461).
    next_exploration_ids = None
    completed_exp_ids = None
    if user_id:
        completed_exp_ids = (
            collection_services.get_valid_completed_exploration_ids(
                user_id, collection_id, collection))
        next_exploration_ids = collection.get_next_exploration_ids(
            completed_exp_ids)
    else:
        # If the user is not logged in or they have not completed any of
        # the explorations yet within the context of this collection,
        # recommend the initial explorations.
        next_exploration_ids = collection.init_exploration_ids
        completed_exp_ids = []

    collection_dict = collection.to_dict()
    collection_dict['skills'] = collection.skills
    collection_dict['playthrough_dict'] = {
        'next_exploration_ids': next_exploration_ids,
        'completed_exploration_ids': completed_exp_ids
    }
    collection_dict['version'] = collection.version
    collection_is_public = rights_manager.is_collection_public(collection_id)

    # Insert an 'exploration' dict into each collection node, where the
    # dict includes meta information about the exploration (ID and title).
    for collection_node in collection_dict['nodes']:
        exploration_id = collection_node['exploration_id']
        summary_dict = exp_summaries_dict_map.get(exploration_id)
        if not allow_invalid_explorations:
            if not summary_dict:
                raise utils.ValidationError(
                    'Expected collection to only reference valid '
                    'explorations, but found an exploration with ID: %s (was '
                    'the exploration deleted or is it a private exploration '
                    'that you do not have edit access to?)' % exploration_id)
            if collection_is_public and rights_manager.is_exploration_private(
                    exploration_id):
                raise utils.ValidationError(
                    'Cannot reference a private exploration within a public '
                    'collection, exploration ID: %s' % exploration_id)

        if summary_dict:
            collection_node['exploration_summary'] = summary_dict
        else:
            collection_node['exploration_summary'] = None

    return collection_dict
def _save_collection(committer_id, collection, commit_message, change_list):
    """Validates an collection and commits it to persistent storage.

    If successful, increments the version number of the incoming collection
    domain object by 1.
    """
    if not change_list:
        raise Exception(
            'Unexpected error: received an invalid change list when trying to '
            'save collection %s: %s' % (collection.id, change_list))

    collection_rights = rights_manager.get_collection_rights(collection.id)
    if collection_rights.status != rights_manager.ACTIVITY_STATUS_PRIVATE:
        collection.validate(strict=True)
    else:
        collection.validate(strict=False)

    # Validate that all explorations referenced by the collection exist.
    exp_ids = collection.exploration_ids
    exp_summaries = (
        exp_services.get_exploration_summaries_matching_ids(exp_ids))
    exp_summaries_dict = {
        exp_id: exp_summaries[ind] for (ind, exp_id) in enumerate(exp_ids)
    }
    for collection_node in collection.nodes:
        if not exp_summaries_dict[collection_node.exploration_id]:
            raise utils.ValidationError(
                'Expected collection to only reference valid explorations, '
                'but found an exploration with ID: %s (was it deleted?)' %
                collection_node.exploration_id)

    # Ensure no explorations are being added that are 'below' the public status
    # of this collection. If the collection is private, it can have both
    # private and public explorations. If it's public, it can only have public
    # explorations.
    # TODO(bhenning): Ensure the latter is enforced above when trying to
    # publish a collection.
    if rights_manager.is_collection_public(collection.id):
        validate_exps_in_collection_are_public(collection)

    collection_model = collection_models.CollectionModel.get(
        collection.id, strict=False)
    if collection_model is None:
        collection_model = collection_models.CollectionModel(id=collection.id)
    else:
        if collection.version > collection_model.version:
            raise Exception(
                'Unexpected error: trying to update version %s of collection '
                'from version %s. Please reload the page and try again.'
                % (collection_model.version, collection.version))
        elif collection.version < collection_model.version:
            raise Exception(
                'Trying to update version %s of collection from version %s, '
                'which is too old. Please reload the page and try again.'
                % (collection_model.version, collection.version))

    collection_model.category = collection.category
    collection_model.title = collection.title
    collection_model.objective = collection.objective
    collection_model.language_code = collection.language_code
    collection_model.tags = collection.tags
    collection_model.schema_version = collection.schema_version
    collection_model.nodes = [
        collection_node.to_dict() for collection_node in collection.nodes
    ]
    collection_model.node_count = len(collection_model.nodes)
    collection_model.commit(committer_id, commit_message, change_list)
    memcache_services.delete(_get_collection_memcache_key(collection.id))
    index_collections_given_ids([collection.id])

    collection.version += 1