Beispiel #1
0
def _get_thread_and_context(request, thread_id, retrieve_kwargs=None):
    """
    Retrieve the given thread and build a serializer context for it, returning
    both. This function also enforces access control for the thread (checking
    both the user's access to the course and to the thread's cohort if
    applicable). Raises ThreadNotFoundError if the thread does not exist or the
    user cannot access it.
    """
    retrieve_kwargs = retrieve_kwargs or {}
    try:
        if "with_responses" not in retrieve_kwargs:
            retrieve_kwargs["with_responses"] = False
        if "mark_as_read" not in retrieve_kwargs:
            retrieve_kwargs["mark_as_read"] = False
        cc_thread = Thread(id=thread_id).retrieve(**retrieve_kwargs)
        course_key = CourseKey.from_string(cc_thread["course_id"])
        course = _get_course(course_key, request.user)
        context = get_context(course, request, cc_thread)
        course_discussion_settings = get_course_discussion_settings(course_key)
        if (not context["is_requester_privileged"]
                and cc_thread["group_id"] and is_commentable_divided(
                    course.id, cc_thread["commentable_id"],
                    course_discussion_settings)):
            requester_group_id = get_group_id_for_user(
                request.user, course_discussion_settings)
            if requester_group_id is not None and cc_thread[
                    "group_id"] != requester_group_id:
                raise ThreadNotFoundError("Thread not found.")
        return cc_thread, context
    except CommentClientRequestError:
        # params are validated at a higher level, so the only possible request
        # error is if the thread doesn't exist
        raise ThreadNotFoundError("Thread not found.")
Beispiel #2
0
def _get_thread_and_context(request, thread_id, retrieve_kwargs=None):
    """
    Retrieve the given thread and build a serializer context for it, returning
    both. This function also enforces access control for the thread (checking
    both the user's access to the course and to the thread's cohort if
    applicable). Raises ThreadNotFoundError if the thread does not exist or the
    user cannot access it.
    """
    retrieve_kwargs = retrieve_kwargs or {}
    try:
        if "with_responses" not in retrieve_kwargs:
            retrieve_kwargs["with_responses"] = False
        if "mark_as_read" not in retrieve_kwargs:
            retrieve_kwargs["mark_as_read"] = False
        cc_thread = Thread(id=thread_id).retrieve(**retrieve_kwargs)
        course_key = CourseKey.from_string(cc_thread["course_id"])
        course = _get_course(course_key, request.user)
        context = get_context(course, request, cc_thread)
        course_discussion_settings = get_course_discussion_settings(course_key)
        if (
                not context["is_requester_privileged"] and
                cc_thread["group_id"] and
                is_commentable_divided(course.id, cc_thread["commentable_id"], course_discussion_settings)
        ):
            requester_group_id = get_group_id_for_user(request.user, course_discussion_settings)
            if requester_group_id is not None and cc_thread["group_id"] != requester_group_id:
                raise ThreadNotFoundError("Thread not found.")
        return cc_thread, context
    except CommentClientRequestError:
        # params are validated at a higher level, so the only possible request
        # error is if the thread doesn't exist
        raise ThreadNotFoundError("Thread not found.")
Beispiel #3
0
def create_thread(request, thread_data):
    """
    Create a thread.

    Arguments:

        request: The django request object used for build_absolute_uri and
          determining the requesting user.

        thread_data: The data for the created thread.

    Returns:

        The created thread; see discussion.rest_api.views.ThreadViewSet for more
        detail.
    """
    course_id = thread_data.get("course_id")
    user = request.user
    if not course_id:
        raise ValidationError({"course_id": ["This field is required."]})
    try:
        course_key = CourseKey.from_string(course_id)
        course = _get_course(course_key, user)
    except InvalidKeyError:
        raise ValidationError({"course_id": ["Invalid value."]})  # lint-amnesty, pylint: disable=raise-missing-from

    if not discussion_open_for_user(course, user):
        raise DiscussionBlackOutException

    context = get_context(course, request)
    _check_initializable_thread_fields(thread_data, context)
    discussion_settings = get_course_discussion_settings(course_key)
    if ("group_id" not in thread_data and is_commentable_divided(
            course_key, thread_data.get("topic_id"), discussion_settings)):
        thread_data = thread_data.copy()
        thread_data["group_id"] = get_group_id_for_user(
            user, discussion_settings)
    serializer = ThreadSerializer(data=thread_data, context=context)
    actions_form = ThreadActionsForm(thread_data)
    if not (serializer.is_valid() and actions_form.is_valid()):
        raise ValidationError(
            dict(
                list(serializer.errors.items()) +
                list(actions_form.errors.items())))
    serializer.save()
    cc_thread = serializer.instance
    thread_created.send(sender=None, user=user, post=cc_thread)
    api_thread = serializer.data
    _do_extra_actions(api_thread, cc_thread, list(thread_data.keys()),
                      actions_form, context, request)

    track_thread_created_event(request, course, cc_thread,
                               actions_form.cleaned_data["following"])

    return api_thread
Beispiel #4
0
def _find_thread(request, course, discussion_id, thread_id):
    """
    Finds the discussion thread with the specified ID.

    Args:
        request: The Django request.
        course_id: The ID of the owning course.
        discussion_id: The ID of the owning discussion.
        thread_id: The ID of the thread.

    Returns:
        The thread in question if the user can see it, else None.
    """
    try:
        thread = cc.Thread.find(thread_id).retrieve(
            with_responses=request.is_ajax(),
            recursive=request.is_ajax(),
            user_id=request.user.id,
            response_skip=request.GET.get("resp_skip"),
            response_limit=request.GET.get("resp_limit"))
    except cc.utils.CommentClientRequestError:
        log.info(
            u"Discussion Error: Thread ID:{thread_id} not found for Discussion: {discussion_id}"
            .format(thread_id=thread_id, discussion_id=discussion_id))
        return None
    # Verify that the student has access to this thread if belongs to a course discussion module
    thread_context = getattr(thread, "context", "course")
    if thread_context == "course" and not utils.discussion_category_id_access(
            course, request.user, discussion_id):
        log.info(
            u'Discussion Error: Thread Context:{context} for thread: {thread}'.
            format(context=thread_context, thread=thread.__dict__))
        return None

    # verify that the thread belongs to the requesting student's group
    is_moderator = has_permission(request.user, "see_all_cohorts", course.id)
    course_discussion_settings = get_course_discussion_settings(course.id)
    if is_commentable_divided(course.id, discussion_id,
                              course_discussion_settings) and not is_moderator:
        user_group_id = get_group_id_for_user(request.user,
                                              course_discussion_settings)
        if getattr(thread, "group_id",
                   None) is not None and user_group_id != thread.group_id:
            log.info(
                u"Discussion Error: user_group:{user_group} is not equal to thread_group:{thread_group}"
                .format(user_group=user_group_id,
                        thread_group=thread.group_id))
            return None

    return thread
Beispiel #5
0
def inline_discussion(request, course_key, discussion_id):
    """
    Renders JSON for DiscussionModules
    """
    with function_trace('get_course_and_user_info'):
        course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
        cc_user = cc.User.from_django_user(request.user)
        user_info = cc_user.to_dict()

    try:
        with function_trace('get_threads'):
            threads, query_params = get_threads(
                request, course, user_info, discussion_id, per_page=INLINE_THREADS_PER_PAGE
            )
    except ValueError:
        return HttpResponseServerError('Invalid group_id')
    except TeamDiscussionHiddenFromUserException:
        return HttpResponseForbidden(TEAM_PERMISSION_MESSAGE)

    with function_trace('get_metadata_for_threads'):
        annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)

    with function_trace('determine_group_permissions'):
        is_staff = has_permission(request.user, 'openclose_thread', course.id)
        course_discussion_settings = get_course_discussion_settings(course.id)
        group_names_by_id = get_group_names_by_id(course_discussion_settings)
        course_is_divided = course_discussion_settings.division_scheme is not CourseDiscussionSettings.NONE

    with function_trace('prepare_content'):
        threads = [
            utils.prepare_content(
                thread,
                course_key,
                is_staff,
                course_is_divided,
                group_names_by_id
            ) for thread in threads
        ]

    return utils.JsonResponse({
        'is_commentable_divided': is_commentable_divided(course_key, discussion_id),
        'discussion_data': threads,
        'user_info': user_info,
        'user_group_id': get_group_id_for_user(request.user, course_discussion_settings),
        'annotated_content_info': annotated_content_info,
        'page': query_params['page'],
        'num_pages': query_params['num_pages'],
        'roles': utils.get_role_ids(course_key),
        'course_settings': make_course_settings(course, request.user, False)
    })
Beispiel #6
0
def inline_discussion(request, course_key, discussion_id):
    """
    Renders JSON for DiscussionModules
    """

    with function_trace('get_course_and_user_info'):
        course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
        cc_user = cc.User.from_django_user(request.user)
        user_info = cc_user.to_dict()

    try:
        with function_trace('get_threads'):
            threads, query_params = get_threads(
                request, course, user_info, discussion_id, per_page=INLINE_THREADS_PER_PAGE
            )
    except ValueError:
        return HttpResponseServerError('Invalid group_id')

    with function_trace('get_metadata_for_threads'):
        annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)

    with function_trace('determine_group_permissions'):
        is_staff = has_permission(request.user, 'openclose_thread', course.id)
        course_discussion_settings = get_course_discussion_settings(course.id)
        group_names_by_id = get_group_names_by_id(course_discussion_settings)
        course_is_divided = course_discussion_settings.division_scheme is not CourseDiscussionSettings.NONE

    with function_trace('prepare_content'):
        threads = [
            utils.prepare_content(
                thread,
                course_key,
                is_staff,
                course_is_divided,
                group_names_by_id
            ) for thread in threads
        ]

    return utils.JsonResponse({
        'is_commentable_divided': is_commentable_divided(course_key, discussion_id),
        'discussion_data': threads,
        'user_info': user_info,
        'user_group_id': get_group_id_for_user(request.user, course_discussion_settings),
        'annotated_content_info': annotated_content_info,
        'page': query_params['page'],
        'num_pages': query_params['num_pages'],
        'roles': utils.get_role_ids(course_key),
        'course_settings': make_course_settings(course, request.user, False)
    })
Beispiel #7
0
def create_thread(request, thread_data):
    """
    Create a thread.

    Arguments:

        request: The django request object used for build_absolute_uri and
          determining the requesting user.

        thread_data: The data for the created thread.

    Returns:

        The created thread; see discussion.rest_api.views.ThreadViewSet for more
        detail.
    """
    course_id = thread_data.get("course_id")
    user = request.user
    if not course_id:
        raise ValidationError({"course_id": ["This field is required."]})
    try:
        course_key = CourseKey.from_string(course_id)
        course = _get_course(course_key, user)
    except InvalidKeyError:
        raise ValidationError({"course_id": ["Invalid value."]})

    context = get_context(course, request)
    _check_initializable_thread_fields(thread_data, context)
    discussion_settings = get_course_discussion_settings(course_key)
    if (
            "group_id" not in thread_data and
            is_commentable_divided(course_key, thread_data.get("topic_id"), discussion_settings)
    ):
        thread_data = thread_data.copy()
        thread_data["group_id"] = get_group_id_for_user(user, discussion_settings)
    serializer = ThreadSerializer(data=thread_data, context=context)
    actions_form = ThreadActionsForm(thread_data)
    if not (serializer.is_valid() and actions_form.is_valid()):
        raise ValidationError(dict(serializer.errors.items() + actions_form.errors.items()))
    serializer.save()
    cc_thread = serializer.instance
    thread_created.send(sender=None, user=user, post=cc_thread)
    api_thread = serializer.data
    _do_extra_actions(api_thread, cc_thread, thread_data.keys(), actions_form, context, request)

    track_thread_created_event(request, course, cc_thread, actions_form.cleaned_data["following"])

    return api_thread
Beispiel #8
0
def _find_thread(request, course, discussion_id, thread_id):
    """
    Finds the discussion thread with the specified ID.

    Args:
        request: The Django request.
        course_id: The ID of the owning course.
        discussion_id: The ID of the owning discussion.
        thread_id: The ID of the thread.

    Returns:
        The thread in question if the user can see it, else None.
    """
    try:
        thread = cc.Thread.find(thread_id).retrieve(
            with_responses=request.is_ajax(),
            recursive=request.is_ajax(),
            user_id=request.user.id,
            response_skip=request.GET.get("resp_skip"),
            response_limit=request.GET.get("resp_limit")
        )
    except cc.utils.CommentClientRequestError:
        log.info(u"Discussion Error: Thread ID:{thread_id} not found for Discussion: {discussion_id}".format(
            thread_id=thread_id, discussion_id=discussion_id)
        )
        return None
    # Verify that the student has access to this thread if belongs to a course discussion module
    thread_context = getattr(thread, "context", "course")
    if thread_context == "course" and not utils.discussion_category_id_access(course, request.user, discussion_id):
        log.info(u'Discussion Error: Thread Context:{context} for thread: {thread}'.format(
            context=thread_context, thread=thread.__dict__)
        )
        return None

    # verify that the thread belongs to the requesting student's group
    is_moderator = has_permission(request.user, "see_all_cohorts", course.id)
    course_discussion_settings = get_course_discussion_settings(course.id)
    if is_commentable_divided(course.id, discussion_id, course_discussion_settings) and not is_moderator:
        user_group_id = get_group_id_for_user(request.user, course_discussion_settings)
        if getattr(thread, "group_id", None) is not None and user_group_id != thread.group_id:
            log.info(u"Discussion Error: user_group:{user_group} is not equal to thread_group:{thread_group}".format(
                user_group=user_group_id, thread_group=thread.group_id
            ))
            return None

    return thread
Beispiel #9
0
def _create_discussion_board_context(request, base_context, thread=None):
    """
    Returns the template context for rendering the discussion board.
    """
    context = base_context.copy()
    course = context['course']
    course_key = course.id
    thread_id = thread.id if thread else None
    discussion_id = thread.commentable_id if thread else None
    course_settings = context['course_settings']
    user = context['user']
    cc_user = cc.User.from_django_user(user)
    user_info = context['user_info']
    if thread:
        _check_team_discussion_access(request, course, discussion_id)
        # Since we're in page render mode, and the discussions UI will request the thread list itself,
        # we need only return the thread information for this one.
        threads = [thread.to_dict()]

        for thread in threads:
            # patch for backward compatibility with comments service
            if "pinned" not in thread:
                thread["pinned"] = False
        thread_pages = 1
        root_url = reverse('forum_form_discussion',
                           args=[six.text_type(course.id)])
    else:
        threads, query_params = get_threads(
            request, course, user_info)  # This might process a search query
        thread_pages = query_params['num_pages']
        root_url = request.path
    is_staff = has_permission(user, 'openclose_thread', course.id)
    threads = [
        utils.prepare_content(thread, course_key, is_staff)
        for thread in threads
    ]

    with function_trace("get_metadata_for_threads"):
        annotated_content_info = utils.get_metadata_for_threads(
            course_key, threads, user, user_info)

    with function_trace("add_courseware_context"):
        add_courseware_context(threads, course, user)

    with function_trace("get_cohort_info"):
        course_discussion_settings = get_course_discussion_settings(course_key)
        user_group_id = get_group_id_for_user(user, course_discussion_settings)

    context.update({
        'root_url':
        root_url,
        'discussion_id':
        discussion_id,
        'thread_id':
        thread_id,
        'threads':
        threads,
        'thread_pages':
        thread_pages,
        'annotated_content_info':
        annotated_content_info,
        'is_moderator':
        has_permission(user, "see_all_cohorts", course_key),
        'groups':
        course_settings[
            "groups"],  # still needed to render _thread_list_template
        'user_group_id':
        user_group_id,  # read from container in NewPostView
        'sort_preference':
        cc_user.default_sort_key,
        'category_map':
        course_settings["category_map"],
        'course_settings':
        course_settings,
        'is_commentable_divided':
        is_commentable_divided(course_key, discussion_id,
                               course_discussion_settings),
        # If the default topic id is None the front-end code will look for a topic that contains "General"
        'discussion_default_topic_id':
        _get_discussion_default_topic_id(course),
        'enable_daily_digest':
        is_forum_daily_digest_enabled()
    })
    context.update(get_experiment_user_metadata_context(
        course,
        user,
    ))
    return context
Beispiel #10
0
def _create_discussion_board_context(request, base_context, thread=None):
    """
    Returns the template context for rendering the discussion board.
    """
    context = base_context.copy()
    course = context['course']
    course_key = course.id
    thread_id = thread.id if thread else None
    discussion_id = thread.commentable_id if thread else None
    course_settings = context['course_settings']
    user = context['user']
    cc_user = cc.User.from_django_user(user)
    user_info = context['user_info']
    if thread:

        # Since we're in page render mode, and the discussions UI will request the thread list itself,
        # we need only return the thread information for this one.
        threads = [thread.to_dict()]

        for thread in threads:
            # patch for backward compatibility with comments service
            if "pinned" not in thread:
                thread["pinned"] = False
        thread_pages = 1
        root_url = reverse('forum_form_discussion', args=[unicode(course.id)])
    else:
        threads, query_params = get_threads(request, course, user_info)   # This might process a search query
        thread_pages = query_params['num_pages']
        root_url = request.path
    is_staff = has_permission(user, 'openclose_thread', course.id)
    threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]

    with function_trace("get_metadata_for_threads"):
        annotated_content_info = utils.get_metadata_for_threads(course_key, threads, user, user_info)

    with function_trace("add_courseware_context"):
        add_courseware_context(threads, course, user)

    with function_trace("get_cohort_info"):
        course_discussion_settings = get_course_discussion_settings(course_key)
        user_group_id = get_group_id_for_user(user, course_discussion_settings)

    context.update({
        'root_url': root_url,
        'discussion_id': discussion_id,
        'thread_id': thread_id,
        'threads': threads,
        'thread_pages': thread_pages,
        'annotated_content_info': annotated_content_info,
        'is_moderator': has_permission(user, "see_all_cohorts", course_key),
        'groups': course_settings["groups"],  # still needed to render _thread_list_template
        'user_group_id': user_group_id,  # read from container in NewPostView
        'sort_preference': cc_user.default_sort_key,
        'category_map': course_settings["category_map"],
        'course_settings': course_settings,
        'is_commentable_divided': is_commentable_divided(course_key, discussion_id, course_discussion_settings),
        # If the default topic id is None the front-end code will look for a topic that contains "General"
        'discussion_default_topic_id': _get_discussion_default_topic_id(course),
    })
    context.update(
        get_experiment_user_metadata_context(
            course,
            user,
        )
    )
    return context