Ejemplo n.º 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 Http404 if the thread does not exist or the user cannot
    access it.
    """
    retrieve_kwargs = retrieve_kwargs or {}
    try:
        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_or_404(course_key, request.user)
        context = get_context(course, request, cc_thread)
        if (
                not context["is_requester_privileged"] and
                cc_thread["group_id"] and
                is_commentable_cohorted(course.id, cc_thread["commentable_id"])
        ):
            requester_cohort = get_cohort_id(request.user, course.id)
            if requester_cohort is not None and cc_thread["group_id"] != requester_cohort:
                raise Http404
        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 Http404
Ejemplo n.º 2
0
def inline_discussion(request, course_key, discussion_id):
    """
    Renders JSON for DiscussionModules
    """
    nr_transaction = newrelic.agent.current_transaction()

    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:
        threads, query_params = get_threads(request, course, discussion_id, per_page=INLINE_THREADS_PER_PAGE)
    except ValueError:
        return HttpResponseBadRequest("Invalid group_id")

    with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
        annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
    is_staff = has_permission(request.user, 'openclose_thread', course.id)
    threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
    with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
        add_courseware_context(threads, course, request.user)
    return utils.JsonResponse({
        'is_commentable_cohorted': is_commentable_cohorted(course_key, discussion_id),
        'discussion_data': threads,
        'user_info': user_info,
        '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)
    })
Ejemplo n.º 3
0
def get_group_id_for_comments_service(request, course_key, commentable_id=None):
    """
    Given a user requesting content within a `commentable_id`, determine the
    group_id which should be passed to the comments service.

    Returns:
        int: the group_id to pass to the comments service or None if nothing
        should be passed

    Raises:
        ValueError if the requested group_id is invalid
    """
    if commentable_id is None or is_commentable_cohorted(course_key, commentable_id):
        if request.method == "GET":
            requested_group_id = request.GET.get('group_id')
        elif request.method == "POST":
            requested_group_id = request.POST.get('group_id')
        if cached_has_permission(request.user, "see_all_cohorts", course_key):
            if not requested_group_id:
                return None
            try:
                group_id = int(requested_group_id)
                get_cohort_by_id(course_key, group_id)
            except CourseUserGroup.DoesNotExist:
                raise ValueError
        else:
            # regular users always query with their own id.
            group_id = get_cohort_id(request.user, course_key)
        return group_id
    else:
        # Never pass a group_id to the comments service for a non-cohorted
        # commentable
        return None
Ejemplo n.º 4
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 Http404 if the thread does not exist or the user cannot
    access it.
    """
    retrieve_kwargs = retrieve_kwargs or {}
    try:
        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_or_404(course_key, request.user)
        context = get_context(course, request, cc_thread)
        if (not context["is_requester_privileged"]
                and cc_thread["group_id"] and is_commentable_cohorted(
                    course.id, cc_thread["commentable_id"])):
            requester_cohort = get_cohort_id(request.user, course.id)
            if requester_cohort is not None and cc_thread[
                    "group_id"] != requester_cohort:
                raise Http404
        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 Http404
Ejemplo n.º 5
0
def get_group_id_for_comments_service(request, course_key, commentable_id=None):
    """
    Given a user requesting content within a `commentable_id`, determine the
    group_id which should be passed to the comments service.

    Returns:
        int: the group_id to pass to the comments service or None if nothing
        should be passed

    Raises:
        ValueError if the requested group_id is invalid
    """
    if commentable_id is None or is_commentable_cohorted(course_key, commentable_id):
        if request.method == "GET":
            requested_group_id = request.GET.get('group_id')
        elif request.method == "POST":
            requested_group_id = request.POST.get('group_id')
        if has_permission(request.user, "see_all_cohorts", course_key):
            if not requested_group_id:
                return None
            try:
                group_id = int(requested_group_id)
                get_cohort_by_id(course_key, group_id)
            except CourseUserGroup.DoesNotExist:
                raise ValueError
        else:
            # regular users always query with their own id.
            group_id = get_cohort_id(request.user, course_key)
        return group_id
    else:
        # Never pass a group_id to the comments service for a non-cohorted
        # commentable
        return None
Ejemplo n.º 6
0
def inline_discussion(request, course_key, discussion_id):
    """
    Renders JSON for DiscussionModules
    """
    nr_transaction = newrelic.agent.current_transaction()

    course = get_course_with_access(request.user, 'load_forum', course_key)
    cc_user = cc.User.from_django_user(request.user)
    user_info = cc_user.to_dict()

    try:
        threads, query_params = get_threads(request, course_key, discussion_id, per_page=INLINE_THREADS_PER_PAGE)
    except ValueError:
        return HttpResponseBadRequest("Invalid group_id")

    with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
        annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
    is_staff = cached_has_permission(request.user, 'openclose_thread', course.id)
    threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
    with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
        add_courseware_context(threads, course)
    return utils.JsonResponse({
        'is_commentable_cohorted': is_commentable_cohorted(course_key, discussion_id),
        'discussion_data': threads,
        'user_info': user_info,
        '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)
    })
Ejemplo n.º 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_api.views.ThreadViewSet for more
        detail.
    """
    course_id = thread_data.get("course_id")
    if not course_id:
        raise ValidationError({"course_id": ["This field is required."]})
    try:
        course_key = CourseKey.from_string(course_id)
        course = _get_course_or_404(course_key, request.user)
    except (Http404, InvalidKeyError):
        raise ValidationError({"course_id": ["Invalid value."]})

    context = get_context(course, request)
    _check_initializable_thread_fields(thread_data, context)
    if (
            "group_id" not in thread_data and
            is_commentable_cohorted(course_key, thread_data.get("topic_id"))
    ):
        thread_data = thread_data.copy()
        thread_data["group_id"] = get_cohort_id(request.user, course_key)
    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.object
    api_thread = serializer.data
    _do_extra_actions(api_thread, cc_thread, thread_data.keys(), actions_form, context)

    track_forum_event(
        request,
        THREAD_CREATED_EVENT_NAME,
        course,
        cc_thread,
        get_thread_created_event_data(cc_thread, followed=actions_form.cleaned_data["following"])
    )

    return api_thread
Ejemplo n.º 8
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_api.views.ThreadViewSet for more
        detail.
    """
    course_id = thread_data.get("course_id")
    if not course_id:
        raise ValidationError({"course_id": ["This field is required."]})
    try:
        course_key = CourseKey.from_string(course_id)
        course = _get_course_or_404(course_key, request.user)
    except (Http404, InvalidKeyError):
        raise ValidationError({"course_id": ["Invalid value."]})

    context = get_context(course, request)
    _check_initializable_thread_fields(thread_data, context)
    if ("group_id" not in thread_data and is_commentable_cohorted(
            course_key, thread_data.get("topic_id"))):
        thread_data = thread_data.copy()
        thread_data["group_id"] = get_cohort_id(request.user, course_key)
    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.object
    api_thread = serializer.data
    _do_extra_actions(api_thread, cc_thread, thread_data.keys(), actions_form,
                      context)

    track_forum_event(
        request, THREAD_CREATED_EVENT_NAME, course, cc_thread,
        get_thread_created_event_data(
            cc_thread, followed=actions_form.cleaned_data["following"]))

    return api_thread
Ejemplo n.º 9
0
def single_thread(request, course_key, discussion_id, thread_id):
    """
    Renders a response to display a single discussion thread.
    """
    nr_transaction = newrelic.agent.current_transaction()

    course = get_course_with_access(request.user,
                                    'load',
                                    course_key,
                                    check_if_enrolled=True)
    course_settings = make_course_settings(course, request.user)
    cc_user = cc.User.from_django_user(request.user)
    user_info = cc_user.to_dict()
    is_moderator = has_permission(request.user, "see_all_cohorts", course_key)

    # Verify that the student has access to this thread if belongs to a discussion module
    if discussion_id not in utils.get_discussion_categories_ids(
            course, request.user):
        raise Http404

    # Currently, the front end always loads responses via AJAX, even for this
    # page; it would be a nice optimization to avoid that extra round trip to
    # the comments service.
    try:
        thread = cc.Thread.find(thread_id).retrieve(
            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 as e:
        if e.status_code == 404:
            raise Http404
        raise

    # verify that the thread belongs to the requesting student's cohort
    if is_commentable_cohorted(course_key, discussion_id) and not is_moderator:
        user_group_id = get_cohort_id(request.user, course_key)
        if getattr(thread, "group_id",
                   None) is not None and user_group_id != thread.group_id:
            raise Http404

    is_staff = has_permission(request.user, 'openclose_thread', course.id)
    if request.is_ajax():
        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_annotated_content_infos"):
            annotated_content_info = utils.get_annotated_content_infos(
                course_key, thread, request.user, user_info=user_info)
        content = utils.prepare_content(thread.to_dict(), course_key, is_staff)
        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "add_courseware_context"):
            add_courseware_context([content], course, request.user)
        return utils.JsonResponse({
            'content':
            content,
            'annotated_content_info':
            annotated_content_info,
        })

    else:
        try:
            threads, query_params = get_threads(request, course)
        except ValueError:
            return HttpResponseBadRequest("Invalid group_id")
        threads.append(thread.to_dict())

        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "add_courseware_context"):
            add_courseware_context(threads, course, request.user)

        for thread in threads:
            # patch for backward compatibility with comments service
            if "pinned" not in thread:
                thread["pinned"] = False

        threads = [
            utils.prepare_content(thread, course_key, is_staff)
            for thread in threads
        ]

        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_metadata_for_threads"):
            annotated_content_info = utils.get_metadata_for_threads(
                course_key, threads, request.user, user_info)

        with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
            user_cohort = get_cohort_id(request.user, course_key)

        context = {
            'discussion_id':
            discussion_id,
            'csrf':
            csrf(request)['csrf_token'],
            'init':
            '',  # TODO: What is this?
            'user_info':
            _attr_safe_json(user_info),
            'annotated_content_info':
            _attr_safe_json(annotated_content_info),
            'course':
            course,
            #'recent_active_threads': recent_active_threads,
            'course_id':
            course.id.to_deprecated_string(
            ),  # TODO: Why pass both course and course.id to template?
            'thread_id':
            thread_id,
            'threads':
            _attr_safe_json(threads),
            'roles':
            _attr_safe_json(utils.get_role_ids(course_key)),
            'is_moderator':
            is_moderator,
            'thread_pages':
            query_params['num_pages'],
            'is_course_cohorted':
            is_course_cohorted(course_key),
            'flag_moderator':
            (has_permission(request.user, 'openclose_thread', course.id)
             or has_access(request.user, 'staff', course)),
            'cohorts':
            course_settings["cohorts"],
            'user_cohort':
            user_cohort,
            'sort_preference':
            cc_user.default_sort_key,
            'category_map':
            course_settings["category_map"],
            'course_settings':
            _attr_safe_json(course_settings)
        }
        return render_to_response('discussion/index.html', context)
Ejemplo n.º 10
0
def single_thread(request, course_key, discussion_id, thread_id):
    """
    Renders a response to display a single discussion thread.
    """
    nr_transaction = newrelic.agent.current_transaction()

    course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
    course_settings = make_course_settings(course, request.user)
    cc_user = cc.User.from_django_user(request.user)
    user_info = cc_user.to_dict()
    is_moderator = has_permission(request.user, "see_all_cohorts", course_key)

    # Verify that the student has access to this thread if belongs to a discussion module
    if discussion_id not in utils.get_discussion_categories_ids(course, request.user):
        raise Http404

    # Currently, the front end always loads responses via AJAX, even for this
    # page; it would be a nice optimization to avoid that extra round trip to
    # the comments service.
    try:
        thread = cc.Thread.find(thread_id).retrieve(
            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 as e:
        if e.status_code == 404:
            raise Http404
        raise

    # verify that the thread belongs to the requesting student's cohort
    if is_commentable_cohorted(course_key, discussion_id) and not is_moderator:
        user_group_id = get_cohort_id(request.user, course_key)
        if getattr(thread, "group_id", None) is not None and user_group_id != thread.group_id:
            raise Http404

    is_staff = has_permission(request.user, 'openclose_thread', course.id)
    if request.is_ajax():
        with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"):
            annotated_content_info = utils.get_annotated_content_infos(course_key, thread, request.user, user_info=user_info)
        content = utils.prepare_content(thread.to_dict(), course_key, is_staff)
        with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
            add_courseware_context([content], course, request.user)
        return utils.JsonResponse({
            'content': content,
            'annotated_content_info': annotated_content_info,
        })

    else:
        try:
            threads, query_params = get_threads(request, course)
        except ValueError:
            return HttpResponseBadRequest("Invalid group_id")
        threads.append(thread.to_dict())

        with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
            add_courseware_context(threads, course, request.user)

        for thread in threads:
            # patch for backward compatibility with comments service
            if "pinned" not in thread:
                thread["pinned"] = False

        threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]

        with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
            annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)

        with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
            user_cohort = get_cohort_id(request.user, course_key)

        context = {
            'discussion_id': discussion_id,
            'csrf': csrf(request)['csrf_token'],
            'init': '',   # TODO: What is this?
            'user_info': _attr_safe_json(user_info),
            'annotated_content_info': _attr_safe_json(annotated_content_info),
            'course': course,
            #'recent_active_threads': recent_active_threads,
            'course_id': course.id.to_deprecated_string(),   # TODO: Why pass both course and course.id to template?
            'thread_id': thread_id,
            'threads': _attr_safe_json(threads),
            'roles': _attr_safe_json(utils.get_role_ids(course_key)),
            'is_moderator': is_moderator,
            'thread_pages': query_params['num_pages'],
            'is_course_cohorted': is_course_cohorted(course_key),
            'flag_moderator': (
                has_permission(request.user, 'openclose_thread', course.id) or
                has_access(request.user, 'staff', course)
            ),
            'cohorts': course_settings["cohorts"],
            'user_cohort': user_cohort,
            'sort_preference': cc_user.default_sort_key,
            'category_map': course_settings["category_map"],
            'course_settings': _attr_safe_json(course_settings)
        }
        return render_to_response('discussion/index.html', context)
Ejemplo n.º 11
0
def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
    """
    This may raise an appropriate subclass of cc.utils.CommentClientError
    if something goes wrong. It may also raise ValueError if the group_id is invalid.
    """
    default_query_params = {
        'page': 1,
        'per_page': per_page,
        'sort_key': 'date',
        'sort_order': 'desc',
        'text': '',
        'course_id': unicode(course.id),
        'commentable_id': discussion_id,
        'user_id': request.user.id,
        'group_id': get_group_id_for_comments_service(request, course.id, discussion_id),  # may raise ValueError
    }

    # If provided with a discussion id, filter by discussion id in the
    # comments_service.
    if discussion_id is not None:
        default_query_params['commentable_id'] = discussion_id

    if not request.GET.get('sort_key'):
        # If the user did not select a sort key, use their last used sort key
        cc_user = cc.User.from_django_user(request.user)
        cc_user.retrieve()
        # TODO: After the comment service is updated this can just be user.default_sort_key because the service returns the default value
        default_query_params['sort_key'] = cc_user.get('default_sort_key') or default_query_params['sort_key']
    else:
        # If the user clicked a sort key, update their default sort key
        cc_user = cc.User.from_django_user(request.user)
        cc_user.default_sort_key = request.GET.get('sort_key')
        cc_user.save()

    #there are 2 dimensions to consider when executing a search with respect to group id
    #is user a moderator
    #did the user request a group

    #if the user requested a group explicitly, give them that group, otherwise, if mod, show all, else if student, use cohort

    is_cohorted = is_commentable_cohorted(course.id, discussion_id)

    if has_permission(request.user, "see_all_cohorts", course.id):
        group_id = request.GET.get('group_id')
        if group_id in ("all", "None"):
            group_id = None
    else:
        group_id = get_cohort_id(request.user, course.id)
        if not group_id:
            default_query_params['exclude_groups'] = True

    if group_id:
        group_id = int(group_id)
        try:
            CourseUserGroup.objects.get(course_id=course.id, id=group_id)
        except CourseUserGroup.DoesNotExist:
            if not is_cohorted:
                group_id = None
            else:
                raise ValueError("Invalid Group ID")
        default_query_params["group_id"] = group_id

    #so by default, a moderator sees all items, and a student sees his cohort

    query_params = merge_dict(
        default_query_params,
        strip_none(
            extract(
                request.GET,
                [
                    'page',
                    'sort_key',
                    'sort_order',
                    'text',
                    'commentable_ids',
                    'flagged',
                    'unread',
                    'unanswered',
                ]
            )
        )
    )

    if not is_cohorted:
        query_params.pop('group_id', None)

    threads, page, num_pages, corrected_text = cc.Thread.search(query_params)
    threads = _set_group_names(course.id, threads)

    query_params['page'] = page
    query_params['num_pages'] = num_pages
    query_params['corrected_text'] = corrected_text

    return threads, query_params