Ejemplo n.º 1
0
 def test_update_course_discussion_settings(self):
     discussion_settings = CourseDiscussionSettings.get(self.course.id)
     discussion_settings.update({
         'divided_discussions': ['cohorted_topic'],
         'division_scheme': CourseDiscussionSettings.ENROLLMENT_TRACK,
         'always_divide_inline_discussions': True,
     })
     discussion_settings = CourseDiscussionSettings.get(self.course.id)
     assert CourseDiscussionSettings.ENROLLMENT_TRACK == discussion_settings.division_scheme
     assert ['cohorted_topic'] == discussion_settings.divided_discussions
     assert discussion_settings.always_divide_inline_discussions
Ejemplo n.º 2
0
 def update(self, instance: CourseDiscussionSettings, validated_data: dict) -> CourseDiscussionSettings:
     """
     Update and save an existing instance
     """
     if not any(field in validated_data for field in self.fields):
         raise ValidationError('Bad request')
     try:
         instance.update(validated_data)
     except ValueError as e:
         raise ValidationError(str(e)) from e
     return instance
Ejemplo n.º 3
0
def get_context(course, request, thread=None):
    """
    Returns a context appropriate for use with ThreadSerializer or
    (if thread is provided) CommentSerializer.
    """
    # TODO: cache staff_user_ids and ta_user_ids if we need to improve perf
    staff_user_ids = {
        user.id
        for role in Role.objects.filter(
            name__in=[FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR],
            course_id=course.id
        )
        for user in role.users.all()
    }
    ta_user_ids = {
        user.id
        for role in Role.objects.filter(name=FORUM_ROLE_COMMUNITY_TA, course_id=course.id)
        for user in role.users.all()
    }
    requester = request.user
    cc_requester = CommentClientUser.from_django_user(requester).retrieve()
    cc_requester["course_id"] = course.id
    course_discussion_settings = CourseDiscussionSettings.get(course.id)
    return {
        "course": course,
        "request": request,
        "thread": thread,
        "discussion_division_enabled": course_discussion_division_enabled(course_discussion_settings),
        "group_ids_to_names": get_group_names_by_id(course_discussion_settings),
        "is_requester_privileged": requester.id in staff_user_ids or requester.id in ta_user_ids,
        "staff_user_ids": staff_user_ids,
        "ta_user_ids": ta_user_ids,
        "cc_requester": cc_requester,
    }
Ejemplo n.º 4
0
def reported_content_email_notification_enabled(course_key):
    """
    Checks for relevant flag and setting and returns boolean for reported
    content email notification for course
    """
    return bool(ENABLE_REPORTED_CONTENT_EMAIL_NOTIFICATIONS.is_enabled(course_key) and
                CourseDiscussionSettings.get(course_key).reported_content_email_notifications)
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
    """
    course_discussion_settings = CourseDiscussionSettings.get(course_key)
    if commentable_id is None or is_commentable_divided(course_key, commentable_id, course_discussion_settings):
        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
            group_id = int(requested_group_id)
            _verify_group_exists(group_id, course_discussion_settings)
        else:
            # regular users always query with their own id.
            group_id = get_group_id_for_user_from_cache(request.user, course_key)
        return group_id
    else:
        # Never pass a group_id to the comments service for a non-divided
        # commentable
        return None
Ejemplo n.º 6
0
    def post(self, request, course_id, rolename):
        """
        Implement a handler for the POST method.
        """
        kwargs = self._get_request_kwargs(course_id, rolename)
        form = CourseDiscussionRolesForm(kwargs, request_user=request.user)
        if not form.is_valid():
            raise ValidationError(form.errors)

        course_id = form.cleaned_data['course_key']
        rolename = form.cleaned_data['rolename']

        serializer = DiscussionRolesSerializer(data=request.data)
        if not serializer.is_valid():
            raise ValidationError(serializer.errors)

        action = serializer.validated_data['action']
        user = serializer.validated_data['user']
        try:
            update_forum_role(course_id, user, rolename, action)
        except Role.DoesNotExist:
            raise ValidationError(f"Role '{rolename}' does not exist")  # lint-amnesty, pylint: disable=raise-missing-from

        role = form.cleaned_data['role']
        data = {'course_id': course_id, 'users': role.users.all()}
        context = {'course_discussion_settings': CourseDiscussionSettings.get(course_id)}
        serializer = DiscussionRolesListSerializer(data, context=context)
        return Response(serializer.data)
Ejemplo n.º 7
0
 def update(self, instance, validated_data: dict):
     """
     Update and save an existing instance
     """
     save = False
     cohort_settings = {}
     for field, value in validated_data.items():
         if field in self.Meta.fields:
             setattr(instance, field, value)
             save = True
         elif field in self.Meta.fields_cohorts:
             cohort_settings[field] = value
     if cohort_settings:
         discussion_settings = CourseDiscussionSettings.get(instance.id)
         serializer = DiscussionSettingsSerializer(
             discussion_settings,
             context={
                 'course': instance,
                 'settings': discussion_settings,
             },
             data=cohort_settings,
             partial=True,
         )
         if serializer.is_valid(raise_exception=True):
             serializer.save()
     if save:
         modulestore().update_item(instance, self.context['user_id'])
     return instance
Ejemplo n.º 8
0
    def patch(self, request, course_id):
        """
        Implement a handler for the PATCH method.
        """
        if request.content_type != MergePatchParser.media_type:
            raise UnsupportedMediaType(request.content_type)

        kwargs = self._get_request_kwargs(course_id)
        form = CourseDiscussionSettingsForm(kwargs, request_user=request.user)
        if not form.is_valid():
            raise ValidationError(form.errors)

        course = form.cleaned_data['course']
        course_key = form.cleaned_data['course_key']
        discussion_settings = CourseDiscussionSettings.get(course_key)

        serializer = DiscussionSettingsSerializer(
            discussion_settings,
            context={
                'course': course,
                'settings': discussion_settings,
            },
            data=request.data,
            partial=True,
        )
        if not serializer.is_valid():
            raise ValidationError(serializer.errors)
        serializer.save()
        return Response(status=status.HTTP_204_NO_CONTENT)
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 = CourseDiscussionSettings.get(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.")  # lint-amnesty, pylint: disable=raise-missing-from
Ejemplo n.º 10
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 = CourseDiscussionSettings.get(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
Ejemplo n.º 11
0
 def test_get_course_discussion_settings_legacy_settings(self):
     self.course.cohort_config = {
         'cohorted': True,
         'always_cohort_inline_discussions': True,
         'cohorted_discussions': ['foo']
     }
     modulestore().update_item(self.course, ModuleStoreEnum.UserID.system)
     discussion_settings = CourseDiscussionSettings.get(self.course.id)
     assert CourseDiscussionSettings.COHORT == discussion_settings.division_scheme
     assert ['foo'] == discussion_settings.divided_discussions
     assert discussion_settings.always_divide_inline_discussions
Ejemplo n.º 12
0
 def test_get_course_discussion_settings_cohort_settings(self):
     CourseCohortsSettings.objects.get_or_create(
         course_id=self.course.id,
         defaults={
             'is_cohorted': True,
             'always_cohort_inline_discussions': True,
             'cohorted_discussions': ['foo', 'bar']
         })
     discussion_settings = CourseDiscussionSettings.get(self.course.id)
     assert CourseDiscussionSettings.COHORT == discussion_settings.division_scheme
     assert ['foo', 'bar'] == discussion_settings.divided_discussions
     assert discussion_settings.always_divide_inline_discussions
Ejemplo n.º 13
0
def config_course_discussions(
    course,
    discussion_topics={},
    divided_discussions=[],
    always_divide_inline_discussions=False,
    reported_content_email_notifications=False,
):
    """
    Set discussions and configure divided discussions for a course.

    Arguments:
        course: CourseBlock
        discussion_topics (Dict): Discussion topic names. Picks ids and
            sort_keys automatically.
        divided_discussions: Discussion topics to divide. Converts the
            list to use the same ids as discussion topic names.
        always_divide_inline_discussions (bool): Whether inline discussions
            should be divided by default.
        reported_content_email_notifications (bool): Whether email notifications
            are enabled for reported content for moderators.

    Returns:
        Nothing -- modifies course in place.
    """
    def to_id(name):
        """Convert name to id."""
        return topic_name_to_id(course, name)

    discussion_settings = CourseDiscussionSettings.get(course.id)
    discussion_settings.update({
        'divided_discussions': [to_id(name) for name in divided_discussions],
        'always_divide_inline_discussions':
        always_divide_inline_discussions,
        'division_scheme':
        CourseDiscussionSettings.COHORT,
        'reported_content_email_notifications':
        reported_content_email_notifications,
    })

    course.discussion_topics = {
        name: {
            "sort_key": "A",
            "id": to_id(name)
        }
        for name in discussion_topics
    }
    try:
        # Not implemented for XMLModulestore, which is used by test_cohorts.
        modulestore().update_item(course, ModuleStoreEnum.UserID.test)
    except NotImplementedError:
        pass
Ejemplo n.º 14
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 = CourseDiscussionSettings.get(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)
    })
Ejemplo n.º 15
0
    def test_invalid_data_types(self):
        exception_msg_template = "Incorrect field type for `{}`. Type must be `{}`"
        fields = [
            {'name': 'division_scheme', 'type': (str,)[0]},
            {'name': 'always_divide_inline_discussions', 'type': bool},
            {'name': 'divided_discussions', 'type': list}
        ]
        invalid_value = 3.14

        discussion_settings = CourseDiscussionSettings.get(self.course.id)
        for field in fields:
            with pytest.raises(ValueError) as value_error:
                discussion_settings.update({field['name']: invalid_value})

            assert str(value_error.value) == exception_msg_template.format(field['name'], field['type'].__name__)
Ejemplo n.º 16
0
def get_users_with_moderator_roles(context):
    """
    Get all users within the course with moderator roles
    """
    moderators = get_users_with_roles([FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR,
                                       FORUM_ROLE_COMMUNITY_TA], context['course_id'])

    context_thread = cc.Thread.find(context['thread_id'])
    if getattr(context_thread, 'group_id', None) is not None:
        group_moderators = get_users_with_roles([FORUM_ROLE_GROUP_MODERATOR], context['course_id'])
        course_discussion_settings = CourseDiscussionSettings.get(context['course_id'])
        moderators_in_group = [user for user in group_moderators if get_group_id_for_user(
            user, course_discussion_settings) == context_thread.group_id]
        moderators += moderators_in_group

    moderators = set(moderators)
    return moderators
Ejemplo n.º 17
0
    def get(self, request, course_id, rolename):
        """
        Implement a handler for the GET method.
        """
        kwargs = self._get_request_kwargs(course_id, rolename)
        form = CourseDiscussionRolesForm(kwargs, request_user=request.user)

        if not form.is_valid():
            raise ValidationError(form.errors)

        course_id = form.cleaned_data['course_key']
        role = form.cleaned_data['role']

        data = {'course_id': course_id, 'users': role.users.all()}
        context = {'course_discussion_settings': CourseDiscussionSettings.get(course_id)}

        serializer = DiscussionRolesListSerializer(data, context=context)
        return Response(serializer.data)
Ejemplo n.º 18
0
def config_course_cohorts(
    course,
    is_cohorted,
    discussion_division_scheme=CourseDiscussionSettings.COHORT,
    auto_cohorts=[],
    manual_cohorts=[],
):
    """
    Set and configure cohorts for a course.

    Arguments:
        course: CourseBlock
        is_cohorted (bool): Is the course cohorted?
        discussion_division_scheme (String): the division scheme for discussions. Default is
            CourseDiscussionSettings.COHORT.
        auto_cohorts (list): Names of auto cohorts to create.
        manual_cohorts (list): Names of manual cohorts to create.

    Returns:
        Nothing -- modifies course in place.
    """

    set_course_cohorted(course.id, is_cohorted)
    discussion_settings = CourseDiscussionSettings.get(course.id)
    discussion_settings.update({
        'division_scheme': discussion_division_scheme,
    })

    for cohort_name in auto_cohorts:
        cohort = CohortFactory(course_id=course.id, name=cohort_name)
        CourseCohortFactory(course_user_group=cohort,
                            assignment_type=CourseCohort.RANDOM)

    for cohort_name in manual_cohorts:
        cohort = CohortFactory(course_id=course.id, name=cohort_name)
        CourseCohortFactory(course_user_group=cohort,
                            assignment_type=CourseCohort.MANUAL)

    try:
        # Not implemented for XMLModulestore, which is used by test_cohorts.
        modulestore().update_item(course, ModuleStoreEnum.UserID.test)
    except NotImplementedError:
        pass
    def test_cohorted_topic_enrollment_track_invalid_group_id(
            self, mock_request):
        CourseModeFactory.create(course_id=self.course.id,
                                 mode_slug=CourseMode.AUDIT)
        CourseModeFactory.create(course_id=self.course.id,
                                 mode_slug=CourseMode.VERIFIED)
        discussion_settings = CourseDiscussionSettings.get(self.course.id)
        discussion_settings.update({
            'divided_discussions': ['cohorted_topic'],
            'division_scheme':
            CourseDiscussionSettings.ENROLLMENT_TRACK,
            'always_divide_inline_discussions':
            True,
        })

        invalid_id = -1000
        response = self.call_view(mock_request, "cohorted_topic",
                                  self.moderator, invalid_id)  # lint-amnesty, pylint: disable=assignment-from-no-return
        assert response.status_code == 500
Ejemplo n.º 20
0
def make_course_settings(course, user, include_category_map=True):
    """
    Generate a JSON-serializable model for course settings, which will be used to initialize a
    DiscussionCourseSettings object on the client.
    """
    course_discussion_settings = CourseDiscussionSettings.get(course.id)
    group_names_by_id = get_group_names_by_id(course_discussion_settings)
    course_setting = {
        'is_discussion_division_enabled': course_discussion_division_enabled(course_discussion_settings),
        'allow_anonymous': course.allow_anonymous,
        'allow_anonymous_to_peers': course.allow_anonymous_to_peers,
        'groups': [
            {"id": str(group_id), "name": group_name} for group_id, group_name in group_names_by_id.items()
        ]
    }
    if include_category_map:
        course_setting['category_map'] = utils.get_discussion_category_map(course, user)

    return course_setting
Ejemplo n.º 21
0
def is_commentable_divided(course_key,
                           commentable_id,
                           course_discussion_settings=None):
    """
    Args:
        course_key: CourseKey
        commentable_id: string
        course_discussion_settings: CourseDiscussionSettings model instance (optional). If not
            supplied, it will be retrieved via the course_key.

    Returns:
        Bool: is this commentable divided, meaning that learners are divided into
        groups (either Cohorts or Enrollment Tracks) and only see posts within their group?

    Raises:
        Http404 if the course doesn't exist.
    """
    if not course_discussion_settings:
        course_discussion_settings = CourseDiscussionSettings.get(course_key)

    course = get_course_by_id(course_key)

    if not course_discussion_division_enabled(
            course_discussion_settings) or get_team(commentable_id):
        # this is the easy case :)
        ans = False
    elif (commentable_id in course.top_level_discussion_topic_ids
          or course_discussion_settings.always_divide_inline_discussions is
          False):
        # top level discussions have to be manually configured as divided
        # (default is not).
        # Same thing for inline discussions if the default is explicitly set to False in settings
        ans = commentable_id in course_discussion_settings.divided_discussions
    else:
        # inline discussions are divided by default
        ans = True

    log.debug("is_commentable_divided(%s, %s) = {%s}", course_key,
              commentable_id, ans)
    return ans
Ejemplo n.º 22
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:
        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):
        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 = CourseDiscussionSettings.get(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:
            return None

    return thread
 def test(user, per, operator="or"):
     if isinstance(per, str):
         if per in CONDITIONS:
             return _check_condition(user, per, content)
         if 'group_' in per:
             # If a course does not have divided discussions
             # or a course has divided discussions, but the current user's content group does not equal
             # the content group of the commenter/poster,
             # then the current user does not have group edit permissions.
             division_scheme = CourseDiscussionSettings.get(course_id).division_scheme
             if (division_scheme is CourseDiscussionSettings.NONE
                     or user_group_id is None
                     or content_user_group is None
                     or user_group_id != content_user_group):
                 return False
         return has_permission(user, per, course_id=course_id)
     elif isinstance(per, list) and operator in ["and", "or"]:
         results = [test(user, x, operator="and") for x in per]
         if operator == "or":
             return True in results
         elif operator == "and":
             return False not in results
Ejemplo n.º 24
0
    def get(self, request, course_id):
        """
        Implement a handler for the GET method.
        """
        kwargs = self._get_request_kwargs(course_id)
        form = CourseDiscussionSettingsForm(kwargs, request_user=request.user)

        if not form.is_valid():
            raise ValidationError(form.errors)

        course_key = form.cleaned_data['course_key']
        course = form.cleaned_data['course']
        discussion_settings = CourseDiscussionSettings.get(course_key)
        serializer = DiscussionSettingsSerializer(
            discussion_settings,
            context={
                'course': course,
                'settings': discussion_settings,
            },
            partial=True,
        )
        response = Response(serializer.data)
        return response
Ejemplo n.º 25
0
 def to_representation(self, instance) -> dict:
     """
     Serialize data into a dictionary, to be used as a response
     """
     settings = {
         field.name: field.read_json(instance)
         for field in instance.fields.values()
         if field.name in self.Meta.fields
     }
     discussion_settings = CourseDiscussionSettings.get(instance.id)
     serializer = DiscussionSettingsSerializer(
         discussion_settings,
         context={
             'course': instance,
             'settings': discussion_settings,
         },
         partial=True,
     )
     settings.update({
         key: value
         for key, value in serializer.data.items() if key != 'id'
     })
     return settings
Ejemplo n.º 26
0
def create_user_profile_context(request, course_key, user_id):
    """ Generate a context dictionary for the user profile. """
    user = cc.User.from_django_user(request.user)
    course = get_course_with_access(request.user,
                                    'load',
                                    course_key,
                                    check_if_enrolled=True)

    # If user is not enrolled in the course, do not proceed.
    django_user = User.objects.get(id=user_id)
    if not CourseEnrollment.is_enrolled(django_user, course.id):
        raise Http404

    query_params = {
        'page': request.GET.get('page', 1),
        'per_page':
        THREADS_PER_PAGE,  # more than threads_per_page to show more activities
    }

    group_id = get_group_id_for_comments_service(request, course_key)
    if group_id is not None:
        query_params['group_id'] = group_id
        profiled_user = cc.User(id=user_id,
                                course_id=course_key,
                                group_id=group_id)
    else:
        profiled_user = cc.User(id=user_id, course_id=course_key)

    threads, page, num_pages = profiled_user.active_threads(query_params)
    query_params['page'] = page
    query_params['num_pages'] = num_pages

    with function_trace("get_metadata_for_threads"):
        user_info = cc.User.from_django_user(request.user).to_dict()
        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 function_trace("add_courseware_context"):
        add_courseware_context(threads, course, request.user)

        # TODO: LEARNER-3854: If we actually implement Learner Analytics code, this
        #   code was original protected to not run in user_profile() if is_ajax().
        #   Someone should determine if that is still necessary (i.e. was that ever
        #   called as is_ajax()) and clean this up as necessary.
        user_roles = django_user.roles.filter(
            course_id=course.id).order_by("name").values_list(
                "name", flat=True).distinct()

        with function_trace("get_cohort_info"):
            course_discussion_settings = CourseDiscussionSettings.get(
                course_key)
            user_group_id = get_group_id_for_user(request.user,
                                                  course_discussion_settings)

        context = _create_base_discussion_view_context(request, course_key)
        context.update({
            'django_user':
            django_user,
            'django_user_roles':
            user_roles,
            'profiled_user':
            profiled_user.to_dict(),
            'threads':
            threads,
            'user_group_id':
            user_group_id,
            'annotated_content_info':
            annotated_content_info,
            'page':
            query_params['page'],
            'num_pages':
            query_params['num_pages'],
            'sort_preference':
            user.default_sort_key,
            'learner_profile_page_url':
            reverse('learner_profile',
                    kwargs={'username': django_user.username}),
        })
        return context
Ejemplo n.º 27
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:  # lint-amnesty, pylint: disable=redefined-argument-from-local
            # 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=[str(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 = CourseDiscussionSettings.get(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
Ejemplo n.º 28
0
def course_discussions_settings_handler(request, course_key_string):
    """
    The restful handler for divided discussion setting requests. Requires JSON.
    This will raise 404 if user is not staff.
    GET
        Returns the JSON representation of divided discussion settings for the course.
    PATCH
        Updates the divided discussion settings for the course. Returns the JSON representation of updated settings.
    """
    course_key = CourseKey.from_string(course_key_string)
    course = get_course_with_access(request.user, 'staff', course_key)
    discussion_settings = CourseDiscussionSettings.get(course_key)

    if request.method == 'PATCH':
        divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
            course, discussion_settings)

        settings_to_change = {}

        if 'divided_course_wide_discussions' in request.json or 'divided_inline_discussions' in request.json:
            divided_course_wide_discussions = request.json.get(
                'divided_course_wide_discussions',
                divided_course_wide_discussions)
            divided_inline_discussions = request.json.get(
                'divided_inline_discussions', divided_inline_discussions)
            settings_to_change[
                'divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions

        if 'always_divide_inline_discussions' in request.json:
            settings_to_change[
                'always_divide_inline_discussions'] = request.json.get(
                    'always_divide_inline_discussions')
        if 'division_scheme' in request.json:
            settings_to_change['division_scheme'] = request.json.get(
                'division_scheme')

        if not settings_to_change:
            return JsonResponse({"error": "Bad Request"}, 400)

        try:
            if settings_to_change:
                discussion_settings.update(settings_to_change)

        except ValueError as err:
            # Note: error message not translated because it is not exposed to the user (UI prevents this state).
            return JsonResponse({"error": str(err)}, 400)

    divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
        course, discussion_settings)

    return JsonResponse({
        'id':
        discussion_settings.id,
        'divided_inline_discussions':
        divided_inline_discussions,
        'divided_course_wide_discussions':
        divided_course_wide_discussions,
        'always_divide_inline_discussions':
        discussion_settings.always_divide_inline_discussions,
        'division_scheme':
        discussion_settings.division_scheme,
        'available_division_schemes':
        available_division_schemes(course_key)
    })
Ejemplo n.º 29
0
def get_group_id_for_user_from_cache(user, course_id):
    """
    Caches the results of get_group_id_for_user, but serializes the course_id
    instead of the course_discussions_settings object as cache keys.
    """
    return get_group_id_for_user(user, CourseDiscussionSettings.get(course_id))
Ejemplo n.º 30
0
 def test_get_course_discussion_settings(self):
     discussion_settings = CourseDiscussionSettings.get(self.course.id)
     assert CourseDiscussionSettings.NONE == discussion_settings.division_scheme
     assert [] == discussion_settings.divided_discussions
     assert not discussion_settings.always_divide_inline_discussions