Exemplo n.º 1
0
def update_thread(request, thread_id, update_data):
    """
    Update a thread.

    Arguments:

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

        thread_id: The id for the thread to update.

        update_data: The data to update in the thread.

    Returns:

        The updated thread; see discussion_api.views.ThreadViewSet for more
        detail.
    """
    cc_thread, context = _get_thread_and_context(request, thread_id)
    _check_editable_fields(cc_thread, update_data, context)
    serializer = ThreadSerializer(cc_thread, data=update_data, partial=True, context=context)
    actions_form = ThreadActionsForm(update_data)
    if not (serializer.is_valid() and actions_form.is_valid()):
        raise ValidationError(dict(serializer.errors.items() + actions_form.errors.items()))
    # Only save thread object if some of the edited fields are in the thread data, not extra actions
    if set(update_data) - set(actions_form.fields):
        serializer.save()
        thread_edited.send(sender=None, user=request.user, post=cc_thread)
    api_thread = serializer.data
    _do_extra_actions(api_thread, cc_thread, update_data.keys(), actions_form, context)
    return api_thread
 def test_create_missing_field(self):
     for field in self.minimal_data:
         data = self.minimal_data.copy()
         data.pop(field)
         serializer = ThreadSerializer(data=data)
         self.assertFalse(serializer.is_valid())
         self.assertEqual(serializer.errors, {field: ["This field is required."]})
Exemplo n.º 3
0
 def test_create_missing_field(self):
     for field in self.minimal_data:
         data = self.minimal_data.copy()
         data.pop(field)
         serializer = ThreadSerializer(data=data)
         self.assertFalse(serializer.is_valid())
         self.assertEqual(serializer.errors,
                          {field: ["This field is required."]})
Exemplo n.º 4
0
    def test_type(self):
        data = self.minimal_data.copy()
        data["type"] = "question"
        self.save_and_reserialize(data)

        data["type"] = "invalid_type"
        serializer = ThreadSerializer(data=data)
        self.assertFalse(serializer.is_valid())
 def test_create_empty_string(self, value):
     data = self.minimal_data.copy()
     data.update({field: value for field in ["topic_id", "title", "raw_body"]})
     serializer = ThreadSerializer(data=data, context=get_context(self.course, self.request))
     self.assertFalse(serializer.is_valid())
     self.assertEqual(
         serializer.errors, {field: ["This field may not be blank."] for field in ["topic_id", "title", "raw_body"]}
     )
Exemplo n.º 6
0
 def test_create_empty_string(self, value):
     data = self.minimal_data.copy()
     data.update({field: value for field in ["topic_id", "title", "raw_body"]})
     serializer = ThreadSerializer(data=data, context=get_context(self.course, self.request))
     self.assertFalse(serializer.is_valid())
     self.assertEqual(
         serializer.errors,
         {field: ["This field may not be blank."] for field in ["topic_id", "title", "raw_body"]}
     )
Exemplo n.º 7
0
 def save_and_reserialize(self, data):
     """
     Create a serializer with the given data, ensure that it is valid, save
     the result, and return the full thread data from the serializer.
     """
     serializer = ThreadSerializer(data=data, context=get_context(self.course, self.request))
     self.assertTrue(serializer.is_valid())
     serializer.save()
     return serializer.data
Exemplo n.º 8
0
    def test_create_type(self):
        self.register_post_thread_response({"id": "test_id", "username": self.user.username})
        data = self.minimal_data.copy()
        data["type"] = "question"
        self.save_and_reserialize(data)

        data["type"] = "invalid_type"
        serializer = ThreadSerializer(data=data)
        self.assertFalse(serializer.is_valid())
 def test_update_course_id(self):
     serializer = ThreadSerializer(
         self.existing_thread,
         data={"course_id": "some/other/course"},
         partial=True,
         context=get_context(self.course, self.request),
     )
     self.assertFalse(serializer.is_valid())
     self.assertEqual(serializer.errors, {"course_id": ["This field is not allowed in an update."]})
Exemplo n.º 10
0
 def test_update_course_id(self):
     serializer = ThreadSerializer(self.existing_thread,
                                   data={"course_id": "some/other/course"},
                                   partial=True,
                                   context=get_context(
                                       self.course, self.request))
     self.assertFalse(serializer.is_valid())
     self.assertEqual(
         serializer.errors,
         {"course_id": ["This field is not allowed in an update."]})
 def test_update_empty_string(self, value):
     serializer = ThreadSerializer(
         self.existing_thread,
         data={field: value for field in ["topic_id", "title", "raw_body"]},
         partial=True,
         context=get_context(self.course, self.request),
     )
     self.assertFalse(serializer.is_valid())
     self.assertEqual(
         serializer.errors, {field: ["This field may not be blank."] for field in ["topic_id", "title", "raw_body"]}
     )
 def save_and_reserialize(self, data, instance=None):
     """
     Create a serializer with the given data and (if updating) instance,
     ensure that it is valid, save the result, and return the full thread
     data from the serializer.
     """
     serializer = ThreadSerializer(
         instance, data=data, partial=(instance is not None), context=get_context(self.course, self.request)
     )
     self.assertTrue(serializer.is_valid())
     serializer.save()
     return serializer.data
Exemplo n.º 13
0
 def test_update_empty_string(self, value):
     serializer = ThreadSerializer(
         self.existing_thread,
         data={field: value for field in ["topic_id", "title", "raw_body"]},
         partial=True,
         context=get_context(self.course, self.request)
     )
     self.assertFalse(serializer.is_valid())
     self.assertEqual(
         serializer.errors,
         {field: ["This field may not be blank."] for field in ["topic_id", "title", "raw_body"]}
     )
Exemplo n.º 14
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")
    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_or_404(course_key, 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(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
    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)

    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
Exemplo n.º 15
0
 def save_and_reserialize(self, data, instance=None):
     """
     Create a serializer with the given data and (if updating) instance,
     ensure that it is valid, save the result, and return the full thread
     data from the serializer.
     """
     serializer = ThreadSerializer(instance,
                                   data=data,
                                   partial=(instance is not None),
                                   context=get_context(
                                       self.course, self.request))
     self.assertTrue(serializer.is_valid())
     serializer.save()
     return serializer.data
Exemplo n.º 16
0
def create_thread(request, thread_data):
    """
    Create a thread.

    Parameters:

        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 = CourseLocator.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)
    serializer = ThreadSerializer(data=thread_data, context=context)
    extras_form = ThreadCreateExtrasForm(thread_data)
    if not (serializer.is_valid() and extras_form.is_valid()):
        raise ValidationError(dict(serializer.errors.items() + extras_form.errors.items()))
    serializer.save()

    thread = serializer.object
    ret = serializer.data
    following = extras_form.cleaned_data["following"]
    if following:
        context["cc_requester"].follow(thread)
        ret["following"] = True

    track_forum_event(
        request,
        THREAD_CREATED_EVENT_NAME,
        course,
        thread,
        get_thread_created_event_data(thread, followed=following)
    )

    return serializer.data
Exemplo n.º 17
0
 def serialize(self, thread):
     """
     Create a serializer with an appropriate context and use it to serialize
     the given thread, returning the result.
     """
     return ThreadSerializer(thread,
                             context=get_context(self.course,
                                                 self.request)).data
Exemplo n.º 18
0
def update_thread(request, thread_id, update_data):
    """
    Update a thread.

    Arguments:

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

        thread_id: The id for the thread to update.

        update_data: The data to update in the thread.

    Returns:

        The updated thread; see discussion_api.views.ThreadViewSet for more
        detail.
    """
    cc_thread, context = _get_thread_and_context(
        request, thread_id, retrieve_kwargs={"with_responses": True})
    _check_editable_fields(cc_thread, update_data, context)
    serializer = ThreadSerializer(cc_thread,
                                  data=update_data,
                                  partial=True,
                                  context=context)
    actions_form = ThreadActionsForm(update_data)
    if not (serializer.is_valid() and actions_form.is_valid()):
        raise ValidationError(
            dict(serializer.errors.items() + actions_form.errors.items()))
    # Only save thread object if some of the edited fields are in the thread data, not extra actions
    if set(update_data) - set(actions_form.fields):
        serializer.save()
        # signal to update Teams when a user edits a thread
        thread_edited.send(sender=None, user=request.user, post=cc_thread)
    api_thread = serializer.data
    _do_extra_actions(api_thread, cc_thread, update_data.keys(), actions_form,
                      context, request)

    # always return read as True (and therefore unread_comment_count=0) as reasonably
    # accurate shortcut, rather than adding additional processing.
    api_thread['read'] = True
    api_thread['unread_comment_count'] = 0
    return api_thread
Exemplo n.º 19
0
def update_thread(request, thread_id, update_data):
    """
    Update a thread.

    Arguments:

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

        thread_id: The id for the thread to update.

        update_data: The data to update in the thread.

    Returns:

        The updated thread; see discussion_api.views.ThreadViewSet for more
        detail.
    """
    cc_thread, context = _get_thread_and_context(request, thread_id)
    _check_editable_fields(cc_thread, update_data, context)
    serializer = ThreadSerializer(cc_thread, data=update_data, partial=True, context=context)
    actions_form = ThreadActionsForm(update_data)
    if not (serializer.is_valid() and actions_form.is_valid()):
        raise ValidationError(dict(serializer.errors.items() + actions_form.errors.items()))
    # Only save thread object if some of the edited fields are in the thread data, not extra actions
    if set(update_data) - set(actions_form.fields):
        serializer.save()
        # signal to update Teams when a user edits a thread
        thread_edited.send(sender=None, user=request.user, post=cc_thread)
    api_thread = serializer.data
    _do_extra_actions(api_thread, cc_thread, update_data.keys(), actions_form, context, request)

    # always return read as True (and therefore unread_comment_count=0) as reasonably
    # accurate shortcut, rather than adding additional processing.
    api_thread['read'] = True
    api_thread['unread_comment_count'] = 0
    return api_thread
Exemplo n.º 20
0
def get_thread(request, thread_id):
    """
    Retrieve a thread.

    Arguments:

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

        thread_id: The id for the thread to retrieve

    """
    cc_thread, context = _get_thread_and_context(request, thread_id)
    serializer = ThreadSerializer(cc_thread, context=context)
    return serializer.data
Exemplo n.º 21
0
def _serialize_discussion_entities(request, context, discussion_entities,
                                   requested_fields, discussion_entity_type):
    """
    It serializes Discussion Entity (Thread or Comment) and add additional data if requested.

    For a given list of Thread/Comment; it serializes and add additional information to the
    object as per requested_fields list (i.e. profile_image).

    Parameters:

        request: The django request object
        context: The context appropriate for use with the thread or comment
        discussion_entities: List of Thread or Comment objects
        requested_fields: Indicates which additional fields to return
            for each thread.
        discussion_entity_type: DiscussionEntity Enum value for Thread or Comment

    Returns:

        A list of serialized discussion entities
    """
    results = []
    usernames = []
    include_profile_image = _include_profile_image(requested_fields)
    for entity in discussion_entities:
        if discussion_entity_type == DiscussionEntity.thread:
            serialized_entity = ThreadSerializer(entity, context=context).data
        elif discussion_entity_type == DiscussionEntity.comment:
            serialized_entity = CommentSerializer(entity, context=context).data
        results.append(serialized_entity)

        if include_profile_image:
            if serialized_entity['author'] and serialized_entity[
                    'author'] not in usernames:
                usernames.append(serialized_entity['author'])
            if ('endorsed' in serialized_entity
                    and serialized_entity['endorsed']
                    and 'endorsed_by' in serialized_entity
                    and serialized_entity['endorsed_by']
                    and serialized_entity['endorsed_by'] not in usernames):
                usernames.append(serialized_entity['endorsed_by'])

    results = _add_additional_response_fields(request, results, usernames,
                                              discussion_entity_type,
                                              include_profile_image)
    return results
Exemplo n.º 22
0
def get_thread_list(request, course_key, page, page_size):
    """
    Return the list of all discussion threads pertaining to the given course

    Parameters:

    request: The django request objects used for build_absolute_uri
    course_key: The key of the course to get discussion threads for
    page: The page number (1-indexed) to retrieve
    page_size: The number of threads to retrieve per page

    Returns:

    A paginated result containing a list of threads; see
    discussion_api.views.ThreadViewSet for more detail.
    """
    course = _get_course_or_404(course_key, request.user)
    context = get_context(course, request)
    threads, result_page, num_pages, _ = Thread.search({
        "course_id":
        unicode(course.id),
        "group_id":
        (None if context["is_requester_privileged"] else get_cohort_id(
            request.user, course.id)),
        "sort_key":
        "date",
        "sort_order":
        "desc",
        "page":
        page,
        "per_page":
        page_size,
    })
    # The comments service returns the last page of results if the requested
    # page is beyond the last page, but we want be consistent with DRF's general
    # behavior and return a 404 in that case
    if result_page != page:
        raise Http404

    results = [
        ThreadSerializer(thread, context=context).data for thread in threads
    ]
    return get_paginated_data(request, results, page, num_pages)
Exemplo n.º 23
0
def get_thread_list(request,
                    course_key,
                    page,
                    page_size,
                    topic_id_list=None,
                    text_search=None,
                    following=False):
    """
    Return the list of all discussion threads pertaining to the given course

    Parameters:

    request: The django request objects used for build_absolute_uri
    course_key: The key of the course to get discussion threads for
    page: The page number (1-indexed) to retrieve
    page_size: The number of threads to retrieve per page
    topic_id_list: The list of topic_ids to get the discussion threads for
    text_search A text search query string to match
    following: If true, retrieve only threads the requester is following

    Note that topic_id_list, text_search, and following are mutually exclusive.

    Returns:

    A paginated result containing a list of threads; see
    discussion_api.views.ThreadViewSet for more detail.

    Raises:

    ValueError: if more than one of the mutually exclusive parameters is
      provided
    Http404: if the requesting user does not have access to the requested course
      or a page beyond the last is requested
    """
    exclusive_param_count = sum(
        1 for param in [topic_id_list, text_search, following] if param)
    if exclusive_param_count > 1:  # pragma: no cover
        raise ValueError(
            "More than one mutually exclusive param passed to get_thread_list")

    course = _get_course_or_404(course_key, request.user)
    context = get_context(course, request)
    query_params = {
        "group_id":
        (None if context["is_requester_privileged"] else get_cohort_id(
            request.user, course.id)),
        "sort_key":
        "date",
        "sort_order":
        "desc",
        "page":
        page,
        "per_page":
        page_size,
        "text":
        text_search,
    }
    text_search_rewrite = None
    if following:
        threads, result_page, num_pages = context[
            "cc_requester"].subscribed_threads(query_params)
    else:
        query_params["course_id"] = unicode(course.id)
        query_params["commentable_ids"] = ",".join(
            topic_id_list) if topic_id_list else None
        query_params["text"] = text_search
        threads, result_page, num_pages, text_search_rewrite = Thread.search(
            query_params)
    # The comments service returns the last page of results if the requested
    # page is beyond the last page, but we want be consistent with DRF's general
    # behavior and return a 404 in that case
    if result_page != page:
        raise Http404

    results = [
        ThreadSerializer(thread, context=context).data for thread in threads
    ]
    ret = get_paginated_data(request, results, page, num_pages)
    ret["text_search_rewrite"] = text_search_rewrite
    return ret
Exemplo n.º 24
0
def get_thread_list(
        request,
        course_key,
        page,
        page_size,
        topic_id_list=None,
        text_search=None,
        following=False,
        view=None,
        order_by="last_activity_at",
        order_direction="desc",
):
    """
    Return the list of all discussion threads pertaining to the given course

    Parameters:

    request: The django request objects used for build_absolute_uri
    course_key: The key of the course to get discussion threads for
    page: The page number (1-indexed) to retrieve
    page_size: The number of threads to retrieve per page
    topic_id_list: The list of topic_ids to get the discussion threads for
    text_search A text search query string to match
    following: If true, retrieve only threads the requester is following
    view: filters for either "unread" or "unanswered" threads
    order_by: The key in which to sort the threads by. The only values are
        "last_activity_at", "comment_count", and "vote_count". The default is
        "last_activity_at".
    order_direction: The direction in which to sort the threads by. The only
        values are "asc" or "desc". The default is "desc".

    Note that topic_id_list, text_search, and following are mutually exclusive.

    Returns:

    A paginated result containing a list of threads; see
    discussion_api.views.ThreadViewSet for more detail.

    Raises:

    ValidationError: if an invalid value is passed for a field.
    ValueError: if more than one of the mutually exclusive parameters is
      provided
    Http404: if the requesting user does not have access to the requested course
      or a page beyond the last is requested
    """
    exclusive_param_count = sum(1 for param in [topic_id_list, text_search, following] if param)
    if exclusive_param_count > 1:  # pragma: no cover
        raise ValueError("More than one mutually exclusive param passed to get_thread_list")

    cc_map = {"last_activity_at": "date", "comment_count": "comments", "vote_count": "votes"}
    if order_by not in cc_map:
        raise ValidationError({
            "order_by":
                ["Invalid value. '{}' must be 'last_activity_at', 'comment_count', or 'vote_count'".format(order_by)]
        })
    if order_direction not in ["asc", "desc"]:
        raise ValidationError({
            "order_direction": ["Invalid value. '{}' must be 'asc' or 'desc'".format(order_direction)]
        })

    course = _get_course_or_404(course_key, request.user)
    context = get_context(course, request)

    query_params = {
        "user_id": unicode(request.user.id),
        "group_id": (
            None if context["is_requester_privileged"] else
            get_cohort_id(request.user, course.id)
        ),
        "page": page,
        "per_page": page_size,
        "text": text_search,
        "sort_key": cc_map.get(order_by),
        "sort_order": order_direction,
    }

    text_search_rewrite = None

    if view:
        if view in ["unread", "unanswered"]:
            query_params[view] = "true"
        else:
            ValidationError({
                "view": ["Invalid value. '{}' must be 'unread' or 'unanswered'".format(view)]
            })

    if following:
        threads, result_page, num_pages = context["cc_requester"].subscribed_threads(query_params)
    else:
        query_params["course_id"] = unicode(course.id)
        query_params["commentable_ids"] = ",".join(topic_id_list) if topic_id_list else None
        query_params["text"] = text_search
        threads, result_page, num_pages, text_search_rewrite = Thread.search(query_params)
    # The comments service returns the last page of results if the requested
    # page is beyond the last page, but we want be consistent with DRF's general
    # behavior and return a 404 in that case
    if result_page != page:
        raise Http404

    results = [ThreadSerializer(thread, context=context).data for thread in threads]
    ret = get_paginated_data(request, results, page, num_pages)
    ret["text_search_rewrite"] = text_search_rewrite
    return ret