def _create_base_discussion_view_context(request, course_key): """ Returns the default template context for rendering any discussion view. """ user = request.user cc_user = cc.User.from_django_user(user) user_info = cc_user.to_dict() course = get_course_with_access(user, 'load', course_key, check_if_enrolled=True) course_settings = make_course_settings(course, user) uses_bootstrap = USE_BOOTSTRAP_FLAG.is_enabled() return { 'csrf': csrf(request)['csrf_token'], 'course': course, 'user': user, 'user_info': user_info, 'staff_access': bool(has_access(user, 'staff', course)), 'roles': utils.get_role_ids(course_key), 'can_create_comment': has_permission(user, "create_comment", course.id), 'can_create_subcomment': has_permission(user, "create_sub_comment", course.id), 'can_create_thread': has_permission(user, "create_thread", course.id), 'flag_moderator': bool( has_permission(user, 'openclose_thread', course.id) or has_access(user, 'staff', course) ), 'course_settings': course_settings, 'disable_courseware_js': True, 'uses_bootstrap': uses_bootstrap, 'uses_pattern_library': not uses_bootstrap, }
def testPermission(self): name = self.random_str() self.moderator_role.add_permission(name) self.assertTrue(has_permission(self.moderator, name, self.course_id)) self.student_role.add_permission(name) self.assertTrue(has_permission(self.student, name, self.course_id))
def _create_discussion_board_context(request, course_key, discussion_id=None, thread_id=None): """ Returns the template context for rendering the discussion board. """ nr_transaction = newrelic.agent.current_transaction() context = _create_base_discussion_view_context(request, course_key) course = context['course'] course_settings = context['course_settings'] user = context['user'] cc_user = cc.User.from_django_user(user) user_info = context['user_info'] if thread_id: thread = _find_thread(request, course, discussion_id=discussion_id, thread_id=thread_id) if not thread: raise Http404 # 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 newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads(course_key, threads, user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course, user) with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): user_cohort_id = get_cohort_id(user, course_key) 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), 'cohorts': course_settings["cohorts"], # still needed to render _thread_list_template 'user_cohort': user_cohort_id, # read from container in NewPostView 'sort_preference': cc_user.default_sort_key, 'category_map': course_settings["category_map"], 'course_settings': course_settings, }) return context
def forum_form_discussion(request, course_key): """ Renders the main Discussion page, potentially filtered by a search query """ course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) if request.is_ajax(): user = cc.User.from_django_user(request.user) user_info = user.to_dict() try: unsafethreads, query_params = get_threads(request, course, user_info) # This might process a search query is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [utils.prepare_content(thread, course_key, is_staff) for thread in unsafethreads] except cc.utils.CommentClientMaintenanceError: return HttpResponseServerError('Forum is in maintenance mode', status=status.HTTP_503_SERVICE_UNAVAILABLE) 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("add_courseware_context"): add_courseware_context(threads, course, request.user) return utils.JsonResponse({ 'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads' 'annotated_content_info': annotated_content_info, 'num_pages': query_params['num_pages'], 'page': query_params['page'], 'corrected_text': query_params['corrected_text'], }) else: course_id = unicode(course.id) tab_view = CourseTabView() return tab_view.get(request, course_id, 'discussion')
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
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 = get_course_discussion_settings(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(request.user, course_discussion_settings) return group_id else: # Never pass a group_id to the comments service for a non-divided # commentable return None
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) })
def has_permission(self, permission): """ Encapsulates lms specific functionality, as `has_permission` is not importable outside of lms context, namely in tests. :param user: :param str permission: Permission :rtype: bool """ # normal import causes the xmodule_assets command to fail due to circular import - hence importing locally from django_comment_client.permissions import has_permission # pylint: disable=import-error return has_permission(self.django_user, permission, self.course_key)
def un_flag_abuse_for_comment(request, course_id, comment_id): """ given a course_id and comment id, unflag comment for abuse ajax only """ user = cc.User.from_django_user(request.user) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_by_id(course_key) remove_all = bool( has_permission(request.user, "openclose_thread", course_key) or has_access(request.user, "staff", course) ) comment = cc.Comment.find(comment_id) comment.unFlagAbuse(user, comment, remove_all) return JsonResponse(prepare_content(comment.to_dict(), course_key))
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) })
def un_flag_abuse_for_thread(request, course_id, thread_id): """ given a course id and thread id, remove abuse flag for this thread ajax only """ user = cc.User.from_django_user(request.user) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_by_id(course_key) thread = cc.Thread.find(thread_id) remove_all = bool( has_permission(request.user, "openclose_thread", course_key) or has_access(request.user, "staff", course) ) thread.unFlagAbuse(user, thread, remove_all) return JsonResponse(prepare_content(thread.to_dict(), course_key))
def un_flag_abuse_for_comment(request, course_id, comment_id): """ given a course_id and comment id, unflag comment for abuse ajax only """ user = cc.User.from_django_user(request.user) course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key) remove_all = bool( has_permission(request.user, 'openclose_thread', course_key) or has_access(request.user, 'staff', course) ) comment = cc.Comment.find(comment_id) comment.unFlagAbuse(user, comment, remove_all) thread_or_comment_flagged.send(sender=None, user=request.user, post=comment, undo=True) return JsonResponse(prepare_content(comment.to_dict(), course_key))
def un_flag_abuse_for_thread(request, course_id, thread_id): """ given a course id and thread id, remove abuse flag for this thread ajax only """ user = cc.User.from_django_user(request.user) course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key) thread = cc.Thread.find(thread_id) remove_all = bool( has_permission(request.user, 'openclose_thread', course_key) or has_access(request.user, 'staff', course) ) thread.unFlagAbuse(user, thread, remove_all) thread_or_comment_flagged.send(sender=None, user=request.user, post=thread, undo=True) return JsonResponse(prepare_content(thread.to_dict(), course_key))
def single_thread(request, course_key, discussion_id, thread_id): """ Renders a response to display a single discussion thread. This could either be a page refresh after navigating to a single thread, a direct link to a single thread, or an AJAX call from the discussions UI loading the responses/comments for a single thread. Depending on the HTTP headers, we'll adjust our response accordingly. """ course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) if request.is_ajax(): cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() is_staff = has_permission(request.user, 'openclose_thread', course.id) thread = _load_thread_for_viewing( request, course, discussion_id=discussion_id, thread_id=thread_id, raise_event=True, ) with function_trace("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 function_trace("add_courseware_context"): add_courseware_context([content], course, request.user) return utils.JsonResponse({ 'content': content, 'annotated_content_info': annotated_content_info, }) else: course_id = unicode(course.id) tab_view = CourseTabView() return tab_view.get(request, course_id, 'discussion', discussion_id=discussion_id, thread_id=thread_id)
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 = 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: return None return thread
def _create_discussion_board_context(request, course_key, discussion_id=None, thread_id=None): """ Returns the template context for rendering the discussion board. """ context = _create_base_discussion_view_context(request, course_key) course = context['course'] course_settings = context['course_settings'] user = context['user'] cc_user = cc.User.from_django_user(user) user_info = context['user_info'] if thread_id: thread = _find_thread(request, course, discussion_id=discussion_id, thread_id=thread_id) if not thread: raise Http404 # 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 newrelic_function_trace("get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads(course_key, threads, user, user_info) with newrelic_function_trace("add_courseware_context"): add_courseware_context(threads, course, user) with newrelic_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), # TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts 'upgrade_link': check_and_get_upgrade_link(request, user, course.id), 'upgrade_price': get_cosmetic_verified_display_price(course), # ENDTODO }) return context
def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None): """ This function is used to pre-process thread and comment models in various ways before adding them to the HTTP response. This includes fixing empty attribute fields, enforcing author anonymity, and enriching metadata around group ownership and response endorsement. @TODO: not all response pre-processing steps are currently integrated into this function. Arguments: content (dict): A thread or comment. course_key (CourseKey): The course key of the course. is_staff (bool): Whether the user is a staff member. course_is_cohorted (bool): Whether the course is cohorted. """ fields = [ 'id', 'title', 'body', 'course_id', 'anonymous', 'anonymous_to_peers', 'endorsed', 'parent_id', 'thread_id', 'votes', 'closed', 'created_at', 'updated_at', 'depth', 'type', 'commentable_id', 'comments_count', 'at_position_list', 'children', 'highlighted_title', 'highlighted_body', 'courseware_title', 'courseware_url', 'unread_comments_count', 'read', 'group_id', 'group_name', 'pinned', 'abuse_flaggers', 'stats', 'resp_skip', 'resp_limit', 'resp_total', 'thread_type', 'endorsed_responses', 'non_endorsed_responses', 'non_endorsed_resp_total', 'endorsement', 'context' ] if (content.get('anonymous') is False) and ((content.get('anonymous_to_peers') is False) or is_staff): fields += ['username', 'user_id'] content = strip_none(extract(content, fields)) if content.get("endorsement"): endorsement = content["endorsement"] endorser = None if endorsement["user_id"]: try: endorser = User.objects.get(pk=endorsement["user_id"]) except User.DoesNotExist: log.error( "User ID %s in endorsement for comment %s but not in our DB.", content.get('user_id'), content.get('id') ) # Only reveal endorser if requester can see author or if endorser is staff if ( endorser and ("username" in fields or has_permission(endorser, "endorse_comment", course_key)) ): endorsement["username"] = endorser.username else: del endorsement["user_id"] if course_is_cohorted is None: course_is_cohorted = is_course_cohorted(course_key) for child_content_key in ["children", "endorsed_responses", "non_endorsed_responses"]: if child_content_key in content: children = [ prepare_content(child, course_key, is_staff, course_is_cohorted=course_is_cohorted) for child in content[child_content_key] ] content[child_content_key] = children if course_is_cohorted: # Augment the specified thread info to include the group name if a group id is present. if content.get('group_id') is not None: content['group_name'] = get_cohort_by_id(course_key, content.get('group_id')).name else: # Remove any cohort information that might remain if the course had previously been cohorted. content.pop('group_id', None) return content
def user_profile(request, course_key, user_id): """ Renders a response to display the user profile page (shown after clicking on a post author's username). """ user = cc.User.from_django_user(request.user) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) try: # 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 } try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseServerError("Invalid group_id") 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 newrelic_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 newrelic_function_trace("add_courseware_context"): add_courseware_context(threads, course, request.user) if request.is_ajax(): return utils.JsonResponse({ 'discussion_data': threads, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'annotated_content_info': annotated_content_info, }) else: user_roles = django_user.roles.filter( course_id=course.id ).order_by("name").values_list("name", flat=True).distinct() with newrelic_function_trace("get_cohort_info"): course_discussion_settings = get_course_discussion_settings(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 render_to_response('discussion/discussion_profile_page.html', context) except User.DoesNotExist: raise Http404
def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None): """ This function is used to pre-process thread and comment models in various ways before adding them to the HTTP response. This includes fixing empty attribute fields, enforcing author anonymity, and enriching metadata around group ownership and response endorsement. @TODO: not all response pre-processing steps are currently integrated into this function. Arguments: content (dict): A thread or comment. course_key (CourseKey): The course key of the course. is_staff (bool): Whether the user is a staff member. course_is_cohorted (bool): Whether the course is cohorted. """ fields = [ 'id', 'title', 'body', 'course_id', 'anonymous', 'anonymous_to_peers', 'endorsed', 'parent_id', 'thread_id', 'votes', 'closed', 'created_at', 'updated_at', 'depth', 'type', 'commentable_id', 'comments_count', 'at_position_list', 'children', 'highlighted_title', 'highlighted_body', 'courseware_title', 'courseware_url', 'unread_comments_count', 'read', 'group_id', 'group_name', 'pinned', 'abuse_flaggers', 'stats', 'resp_skip', 'resp_limit', 'resp_total', 'thread_type', 'endorsed_responses', 'non_endorsed_responses', 'non_endorsed_resp_total', 'endorsement', 'context' ] if (content.get('anonymous') is False) and ( (content.get('anonymous_to_peers') is False) or is_staff): fields += ['username', 'user_id'] content = strip_none(extract(content, fields)) if content.get("endorsement"): endorsement = content["endorsement"] endorser = None if endorsement["user_id"]: try: endorser = User.objects.get(pk=endorsement["user_id"]) except User.DoesNotExist: log.error( "User ID %s in endorsement for comment %s but not in our DB.", content.get('user_id'), content.get('id')) # Only reveal endorser if requester can see author or if endorser is staff if (endorser and ("username" in fields or has_permission(endorser, "endorse_comment", course_key))): endorsement["username"] = endorser.username else: del endorsement["user_id"] if course_is_cohorted is None: course_is_cohorted = is_course_cohorted(course_key) for child_content_key in [ "children", "endorsed_responses", "non_endorsed_responses" ]: if child_content_key in content: children = [ prepare_content(child, course_key, is_staff, course_is_cohorted=course_is_cohorted) for child in content[child_content_key] ] content[child_content_key] = children if course_is_cohorted: # Augment the specified thread info to include the group name if a group id is present. if content.get('group_id') is not None: content['group_name'] = get_cohort_by_id( course_key, content.get('group_id')).name else: # Remove any cohort information that might remain if the course had previously been cohorted. content.pop('group_id', None) return content
def single_thread(request, course_key, discussion_id, thread_id): """ Renders a response to display a single discussion thread. This could either be a page refresh after navigating to a single thread, a direct link to a single thread, or an AJAX call from the discussions UI loading the responses/comments for a single thread. Depending on the HTTP headers, we'll adjust our response accordingly. """ 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) is_staff = has_permission(request.user, 'openclose_thread', course.id) 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 as error: if error.status_code == 404: raise Http404 raise # 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): raise Http404 # 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 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: # 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()] 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': user_info, 'can_create_comment': has_permission(request.user, "create_comment", course.id), 'can_create_subcomment': has_permission(request.user, "create_sub_comment", course.id), 'can_create_thread': has_permission(request.user, "create_thread", course.id), 'annotated_content_info': 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': threads, 'roles': utils.get_role_ids(course_key), 'is_moderator': is_moderator, 'thread_pages': 1, 'is_course_cohorted': is_course_cohorted(course_key), 'flag_moderator': bool( 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': course_settings, 'disable_courseware_js': True, 'uses_pattern_library': True, } return render_to_response('discussion/discussion_board.html', context)
def user_profile(request, course_key, user_id): """ Renders a response to display the user profile page (shown after clicking on a post author's username). """ nr_transaction = newrelic.agent.current_transaction() #TODO: Allow sorting? course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) try: query_params = { 'page': request.GET.get('page', 1), 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities } try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseBadRequest("Invalid group_id") 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 user_info = cc.User.from_django_user(request.user).to_dict() 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] if request.is_ajax(): return utils.JsonResponse({ 'discussion_data': threads, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'annotated_content_info': annotated_content_info, }) else: django_user = User.objects.get(id=user_id) context = { 'course': course, 'user': request.user, 'django_user': django_user, 'profiled_user': profiled_user.to_dict(), 'threads': threads, 'user_info': user_info, 'annotated_content_info': annotated_content_info, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}), 'disable_courseware_js': True, 'uses_pattern_library': True, } return render_to_response('discussion/discussion_profile_page.html', context) except User.DoesNotExist: raise Http404
def forum_form_discussion(request, course_key): """ Renders the main Discussion page, potentially filtered by a search query """ 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) user = cc.User.from_django_user(request.user) user_info = user.to_dict() try: unsafethreads, query_params = get_threads(request, course) # This might process a search query is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [utils.prepare_content(thread, course_key, is_staff) for thread in unsafethreads] except cc.utils.CommentClientMaintenanceError: log.warning("Forum is in maintenance mode") return render_to_response('discussion/maintenance.html', { 'disable_courseware_js': True, 'uses_pattern_library': True, }) 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) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course, request.user) if request.is_ajax(): return utils.JsonResponse({ 'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads' 'annotated_content_info': annotated_content_info, 'num_pages': query_params['num_pages'], 'page': query_params['page'], 'corrected_text': query_params['corrected_text'], }) else: with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): user_cohort_id = get_cohort_id(request.user, course_key) context = { 'csrf': csrf(request)['csrf_token'], 'course': course, #'recent_active_threads': recent_active_threads, 'staff_access': bool(has_access(request.user, 'staff', course)), 'threads': threads, 'thread_pages': query_params['num_pages'], 'user_info': user_info, 'can_create_comment': has_permission(request.user, "create_comment", course.id), 'can_create_subcomment': has_permission(request.user, "create_sub_comment", course.id), 'can_create_thread': has_permission(request.user, "create_thread", course.id), 'flag_moderator': bool( has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, 'staff', course) ), 'annotated_content_info': annotated_content_info, 'course_id': course.id.to_deprecated_string(), 'roles': utils.get_role_ids(course_key), 'is_moderator': has_permission(request.user, "see_all_cohorts", course_key), 'cohorts': course_settings["cohorts"], # still needed to render _thread_list_template 'user_cohort': user_cohort_id, # read from container in NewPostView 'is_course_cohorted': is_course_cohorted(course_key), # still needed to render _thread_list_template 'sort_preference': user.default_sort_key, 'category_map': course_settings["category_map"], 'course_settings': course_settings, 'disable_courseware_js': True, 'uses_pattern_library': True, } # print "start rendering.." return render_to_response('discussion/discussion_board.html', context)
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
def _create_discussion_board_context(request, course_key, discussion_id=None, thread_id=None): """ Returns the template context for rendering the discussion board. """ nr_transaction = newrelic.agent.current_transaction() context = _create_base_discussion_view_context(request, course_key) course = context['course'] course_settings = context['course_settings'] user = context['user'] cc_user = cc.User.from_django_user(user) user_info = context['user_info'] if thread_id: thread = _find_thread(request, course, discussion_id=discussion_id, thread_id=thread_id) if not thread: raise Http404 # 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 newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads( course_key, threads, user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course, user) with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): user_cohort_id = get_cohort_id(user, course_key) 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), 'cohorts': course_settings[ "cohorts"], # still needed to render _thread_list_template 'user_cohort': user_cohort_id, # read from container in NewPostView 'sort_preference': cc_user.default_sort_key, 'category_map': course_settings["category_map"], 'course_settings': course_settings, }) return context
def followed_threads(request, course_key, user_id): """ Ajax-only endpoint retrieving the threads followed by a specific user. """ nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) try: profiled_user = cc.User(id=user_id, course_id=course_key) default_query_params = { 'page': 1, 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities 'sort_key': 'date', 'sort_order': 'desc', } query_params = merge_dict( default_query_params, strip_none( extract( request.GET, [ 'page', 'sort_key', 'sort_order', 'flagged', 'unread', 'unanswered', ] ) ) ) try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseBadRequest("Invalid group_id") if group_id is not None: query_params['group_id'] = group_id threads, page, num_pages = profiled_user.subscribed_threads(query_params) threads = _set_group_names(course.id, threads) query_params['page'] = page query_params['num_pages'] = num_pages user_info = cc.User.from_django_user(request.user).to_dict() 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) if request.is_ajax(): is_staff = has_permission(request.user, 'openclose_thread', course.id) return utils.JsonResponse({ 'annotated_content_info': annotated_content_info, 'discussion_data': [utils.prepare_content(thread, course_key, is_staff) for thread in threads], 'page': query_params['page'], 'num_pages': query_params['num_pages'], }) #TODO remove non-AJAX support, it does not appear to be used and does not appear to work. else: context = { 'course': course, 'user': request.user, 'django_user': User.objects.get(id=user_id), 'profiled_user': profiled_user.to_dict(), 'threads': _attr_safe_json(threads), 'user_info': _attr_safe_json(user_info), 'annotated_content_info': _attr_safe_json(annotated_content_info), # 'content': content, } return render_to_response('discussion/user_profile.html', context) except User.DoesNotExist: raise Http404
def user_profile(request, course_key, user_id): """ Renders a response to display the user profile page (shown after clicking on a post author's username). """ nr_transaction = newrelic.agent.current_transaction() #TODO: Allow sorting? course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) try: query_params = { 'page': request.GET.get('page', 1), 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities } try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseBadRequest("Invalid group_id") 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 user_info = cc.User.from_django_user(request.user).to_dict() 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 ] if request.is_ajax(): return utils.JsonResponse({ 'discussion_data': threads, 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'annotated_content_info': json.dumps(annotated_content_info), }) else: django_user = User.objects.get(id=user_id) context = { 'course': course, 'user': request.user, 'django_user': django_user, 'profiled_user': profiled_user.to_dict(), 'threads': json.dumps(threads), 'user_info': json.dumps(user_info, default=lambda x: None), 'annotated_content_info': json.dumps(annotated_content_info), 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}) } return render_to_response('discussion/user_profile.html', context) except User.DoesNotExist: raise Http404
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) # 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 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): raise Http404 # 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': json.dumps(user_info), 'can_create_comment': json.dumps( has_permission(request.user, "create_comment", course.id)), 'can_create_subcomment': json.dumps( has_permission(request.user, "create_sub_comment", course.id)), 'can_create_thread': has_permission(request.user, "create_thread", course.id), 'annotated_content_info': json.dumps(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': json.dumps(threads), 'roles': json.dumps(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': bool( 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': json.dumps(course_settings) } return render_to_response('discussion/index.html', context)
def followed_threads(request, course_key, user_id): """ Ajax-only endpoint retrieving the threads followed by a specific user. """ nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) try: profiled_user = cc.User(id=user_id, course_id=course_key) default_query_params = { 'page': 1, 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities 'sort_key': 'date', } query_params = merge_dict( default_query_params, strip_none( extract(request.GET, [ 'page', 'sort_key', 'flagged', 'unread', 'unanswered', ]))) try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseBadRequest("Invalid group_id") if group_id is not None: query_params['group_id'] = group_id paginated_results = profiled_user.subscribed_threads(query_params) print "\n \n \n paginated results \n \n \n " print paginated_results query_params['page'] = paginated_results.page query_params['num_pages'] = paginated_results.num_pages user_info = cc.User.from_django_user(request.user).to_dict() with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads( course_key, paginated_results.collection, request.user, user_info) if request.is_ajax(): is_staff = has_permission(request.user, 'openclose_thread', course.id) return utils.JsonResponse({ 'annotated_content_info': annotated_content_info, 'discussion_data': [ utils.prepare_content(thread, course_key, is_staff) for thread in paginated_results.collection ], 'page': query_params['page'], 'num_pages': query_params['num_pages'], }) #TODO remove non-AJAX support, it does not appear to be used and does not appear to work. else: context = { 'course': course, 'user': request.user, 'django_user': User.objects.get(id=user_id), 'profiled_user': profiled_user.to_dict(), 'threads': paginated_results.collection, 'user_info': user_info, 'annotated_content_info': annotated_content_info, # 'content': content, } return render_to_response('discussion/user_profile.html', context) except User.DoesNotExist: raise Http404
def get_threads(request, course, user_info, discussion_id=None, per_page=THREADS_PER_PAGE): """ This may raise an appropriate subclass of cc.utils.CommentClientError if something goes wrong, or ValueError if the group_id is invalid. Arguments: request (WSGIRequest): The user request. course (CourseDescriptorWithMixins): The course object. user_info (dict): The comment client User object as a dict. discussion_id (unicode): Optional discussion id/commentable id for context. per_page (int): Optional number of threads per page. Returns: (tuple of list, dict): A tuple of the list of threads and a dict of the query parameters used for the search. """ default_query_params = { 'page': 1, 'per_page': per_page, 'sort_key': 'activity', 'text': '', 'course_id': unicode(course.id), 'user_id': request.user.id, 'context': ThreadContext.COURSE, '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 # Use the discussion id/commentable id to determine the context we are going to pass through to the backend. if get_team(discussion_id) is not None: default_query_params['context'] = ThreadContext.STANDALONE if not request.GET.get('sort_key'): # If the user did not select a sort key, use their last used sort key default_query_params['sort_key'] = user_info.get('default_sort_key') or default_query_params['sort_key'] elif request.GET.get('sort_key') != user_info.get('default_sort_key'): # 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 if discussion_id: is_cohorted = is_commentable_divided(course.id, discussion_id) else: is_cohorted = is_course_cohorted(course.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', 'text', 'commentable_ids', 'flagged', 'unread', 'unanswered', ] ) ) ) if not is_cohorted: query_params.pop('group_id', None) paginated_results = cc.Thread.search(query_params) threads = paginated_results.collection # If not provided with a discussion id, filter threads by commentable ids # which are accessible to the current user. if discussion_id is None: discussion_category_ids = set(utils.get_discussion_categories_ids(course, request.user)) threads = [ thread for thread in threads if thread.get('commentable_id') in discussion_category_ids ] for thread in threads: # patch for backward compatibility to comments service if 'pinned' not in thread: thread['pinned'] = False query_params['page'] = paginated_results.page query_params['num_pages'] = paginated_results.num_pages query_params['corrected_text'] = paginated_results.corrected_text return threads, query_params
def render_body(context,**pageargs): __M_caller = context.caller_stack._push_frame() try: __M_locals = __M_dict_builtin(pageargs=pageargs) course = context.get('course', UNDEFINED) user = context.get('user', UNDEFINED) settings = context.get('settings', UNDEFINED) __M_writer = context.writer() # SOURCE LINE 1 __M_writer(u'\n') # SOURCE LINE 2 __M_writer(u'\n\n<script type="text/template" id="thread-template">\n <article class="discussion-article" data-id="') # SOURCE LINE 5 __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'">\n <div class="thread-content-wrapper"></div>\n \n <ol class="responses">\n <li class="loading"><div class="loading-animation"></div></li>\n </ol>\n <div class="post-status-closed bottom-post-status" style="display: none">\n ') # SOURCE LINE 12 __M_writer(filters.decode.utf8(_("This thread is closed."))) __M_writer(u'\n </div>\n <!--\n') # SOURCE LINE 15 if course is UNDEFINED or has_permission(user, 'create_comment', course.id): # SOURCE LINE 16 __M_writer(u' <form class="discussion-reply-new" data-id="') __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'">\n <h4>') # SOURCE LINE 17 __M_writer(filters.decode.utf8(_("Start a new discussion:"))) __M_writer(u'</h4>\n <ul class="discussion-errors"></ul>\n <div class="reply-body" data-id="') # SOURCE LINE 19 __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'"></div>\n <div class="reply-post-control">\n <a class="discussion-submit-post control-button" href="#">') # SOURCE LINE 21 __M_writer(filters.decode.utf8(_("Submit"))) __M_writer(u'</a>\n </div>\n </form>\n') # SOURCE LINE 25 __M_writer(u' -->\n </article>\n</script>\n\n<script type="text/template" id="thread-show-template">\n <div class="discussion-post">\n <div><a href="javascript:void(0)" class="dogear action-follow" data-tooltip="follow"></a></div>\n <header>\n ') # SOURCE LINE 33 __M_writer(filters.decode.utf8("<% if (obj.group_id) { %>")) __M_writer(u'\n <div class="group-visibility-label">') # SOURCE LINE 34 __M_writer(filters.decode.utf8("<%- obj.group_string%>")) __M_writer(u'</div>\n ') # SOURCE LINE 35 __M_writer(filters.decode.utf8("<% } %>")) __M_writer(u' \n \n <a href="#" class="vote-btn discussion-vote discussion-vote-up" data-role="discussion-vote" data-tooltip="vote">\n <span class="plus-icon">+</span> <span class=\'votes-count-number\'>') # SOURCE LINE 38 __M_writer(filters.decode.utf8('<%- votes["up_count"] %>')) __M_writer(u'</span></a>\n <h1>') # SOURCE LINE 39 __M_writer(filters.decode.utf8('<%- title %>')) __M_writer(u'</h1>\n <p class="posted-details">\n ') # SOURCE LINE 41 __M_writer(filters.decode.utf8("<% if (obj.username) { %>")) __M_writer(u'\n <a href="') # SOURCE LINE 42 __M_writer(filters.decode.utf8('<%- user_url %>')) __M_writer(u'" class="username">') __M_writer(filters.decode.utf8('<%- username %>')) __M_writer(u'</a>\n ') # SOURCE LINE 43 __M_writer(filters.decode.utf8("<% } else {print('anonymous');} %>")) __M_writer(u'\n <span class="timeago" title="') # SOURCE LINE 44 __M_writer(filters.decode.utf8('<%- created_at %>')) __M_writer(u'">') __M_writer(filters.decode.utf8('<%- created_at %>')) __M_writer(u'</span>\n\n <span class="post-status-closed top-post-status" style="display: none">\n ') # SOURCE LINE 47 __M_writer(filters.decode.utf8(_("• This thread is closed."))) __M_writer(u'\n </span>\n </p>\n </header>\n\n <div class="post-body">') # SOURCE LINE 52 __M_writer(filters.decode.utf8('<%- body %>')) __M_writer(u'</div>\n <div class="discussion-flag-abuse notflagged" data-role="thread-flag" data-tooltip="Report Misuse">\n <i class="icon icon-flag"></i><span class="flag-label">') # SOURCE LINE 54 __M_writer(filters.decode.utf8(_("Report Misuse"))) __M_writer(u'</span></div>\n \n \n') # SOURCE LINE 57 if course and has_permission(user, 'openclose_thread', course.id): # SOURCE LINE 58 __M_writer(u' <div class="admin-pin discussion-pin notpinned" data-role="thread-pin" data-tooltip="pin this thread">\n <i class="icon icon-pushpin"></i><span class="pin-label">') # SOURCE LINE 59 __M_writer(filters.decode.utf8(_("Pin Thread"))) __M_writer(u'</span></div>\n\n') # SOURCE LINE 61 else: # SOURCE LINE 62 __M_writer(u' ') __M_writer(filters.decode.utf8("<% if (pinned) { %>")) __M_writer(u'\n <div class="discussion-pin notpinned" data-role="thread-pin" data-tooltip="pin this thread">\n <i class="icon icon-pushpin"></i><span class="pin-label">') # SOURCE LINE 64 __M_writer(filters.decode.utf8(_("Pin Thread"))) __M_writer(u'</span></div>\n ') # SOURCE LINE 65 __M_writer(filters.decode.utf8("<% } %>")) __M_writer(u' \n') # SOURCE LINE 67 __M_writer(u' \n \n ') # SOURCE LINE 69 __M_writer(filters.decode.utf8('<% if (obj.courseware_url) { %>')) __M_writer(u'\n <div class="post-context">\n (this topic is about <a href="') # SOURCE LINE 71 __M_writer(filters.decode.utf8('<%- courseware_url%>')) __M_writer(u'">') __M_writer(filters.decode.utf8('<%- courseware_title %>')) __M_writer(u' Welcome to Pepper</a>)\n </div>\n ') # SOURCE LINE 73 __M_writer(filters.decode.utf8('<% } %>')) __M_writer(u'\n\n <ul class="moderator-actions">\n <li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> ') # SOURCE LINE 76 __M_writer(filters.decode.utf8(_("Edit"))) __M_writer(u'</a></li>\n <li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> ') # SOURCE LINE 77 __M_writer(filters.decode.utf8(_("Delete"))) __M_writer(u'</a></li>\n <li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> ') # SOURCE LINE 78 __M_writer(filters.decode.utf8(_("Close"))) __M_writer(u'</a></li>\n </ul>\n </div>\n</script>\n\n<script type="text/template" id="thread-edit-template">\n <div class="discussion-post edit-post-form">\n <h1>') # SOURCE LINE 85 __M_writer(filters.decode.utf8(_("Editing topic"))) __M_writer(u'</h1>\n <ul class="edit-post-form-errors"></ul>\n <div class="form-row">\n <input type="text" class="edit-post-title" name="title" value="') # SOURCE LINE 88 __M_writer(filters.decode.utf8("<%-title %>")) __M_writer(u'" placeholder="Title">\n </div>\n <div class="form-row">\n <div class="edit-post-body" name="body">') # SOURCE LINE 91 __M_writer(filters.decode.utf8("<%- body %>")) __M_writer(u'</div>\n </div>\n') # SOURCE LINE 98 __M_writer(u' <input type="submit" class="post-update" value="') __M_writer(filters.decode.utf8(_("Update topic"))) __M_writer(u'">\n <a href="#" class="post-cancel">') # SOURCE LINE 99 __M_writer(filters.decode.utf8(_("Cancel"))) __M_writer(u'</a>\n </div>\n</script>\n\n<script type="text/template" id="thread-response-template">\n <div class="discussion-response"></div>\n <ol class="comments">\n <li class="new-comment response-local">\n <form class="comment-form" data-id="') # SOURCE LINE 107 __M_writer(filters.decode.utf8('<%- wmdId %>')) __M_writer(u'">\n <ul class="discussion-errors"></ul>\n <div class="comment-body" data-id="') # SOURCE LINE 109 __M_writer(filters.decode.utf8('<%- wmdId %>')) __M_writer(u'"\n data-placeholder="Add a comment..."></div>\n <div class="comment-post-control">\n <a class="discussion-submit-comment control-button" href="#">') # SOURCE LINE 112 __M_writer(filters.decode.utf8(_("Submit"))) __M_writer(u'</a>\n </div>\n </form>\n \n </li>\n </ol>\n</script>\n\n<script type="text/template" id="thread-response-show-template">\n <header class="response-local">\n <a href="javascript:void(0)" class="vote-btn" data-tooltip="vote"><span class="plus-icon"></span><span class="votes-count-number">') # SOURCE LINE 122 __M_writer(filters.decode.utf8("<%- votes['up_count'] %>")) __M_writer(u'</span></a>\n <a href="javascript:void(0)" class="endorse-btn') # SOURCE LINE 123 __M_writer(filters.decode.utf8('<% if (endorsed) { %> is-endorsed<% } %>')) __M_writer(u' action-endorse" style="cursor: default; display: none;" data-tooltip="endorse"><span class="check-icon" style="pointer-events: none; "></span></a>\n ') # SOURCE LINE 124 __M_writer(filters.decode.utf8("<% if (obj.username) { %>")) __M_writer(u'\n <a href="') # SOURCE LINE 125 __M_writer(filters.decode.utf8('<%- user_url %>')) __M_writer(u'" class="posted-by" posted_by_id="') __M_writer(filters.decode.utf8('<%- user_id %>')) __M_writer(u'">') __M_writer(filters.decode.utf8('<%- username %>')) __M_writer(u'</a>\n ') # SOURCE LINE 126 __M_writer(filters.decode.utf8("<% } else {print('<span class=\"anonymous\"><em>anonymous</em></span>');} %>")) __M_writer(u'\n <p class="posted-details" title="') # SOURCE LINE 127 __M_writer(filters.decode.utf8('<%- created_at %>')) __M_writer(u'">') __M_writer(filters.decode.utf8('<%- created_at %>')) __M_writer(u'</p>\n </header>\n <div class="response-local"><div class="response-body">') # SOURCE LINE 129 __M_writer(filters.decode.utf8("<%- body %>")) __M_writer(u'</div>\n <div class="discussion-flag-abuse notflagged" data-role="thread-flag" data-tooltip="report misuse"> \n <i class="icon icon-flag"></i><span class="flag-label">Report Misuse</span></div>\n </div>\n <ul class="moderator-actions response-local">\n <li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> ') # SOURCE LINE 134 __M_writer(filters.decode.utf8(_("Edit"))) __M_writer(u'</a></li>\n <li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> ') # SOURCE LINE 135 __M_writer(filters.decode.utf8(_("Delete"))) __M_writer(u'</a></li>\n <li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> ') # SOURCE LINE 136 __M_writer(filters.decode.utf8(_("Close"))) __M_writer(u'</a></li>\n </ul>\n</script>\n\n<script type="text/template" id="thread-response-edit-template">\n <div class="edit-post-form">\n <h1>') # SOURCE LINE 142 __M_writer(filters.decode.utf8(_("Editing response"))) __M_writer(u'</h1>\n <ul class="edit-post-form-errors"></ul>\n <div class="form-row">\n <div class="edit-post-body" name="body">') # SOURCE LINE 145 __M_writer(filters.decode.utf8("<%- body %>")) __M_writer(u'</div>\n </div>\n <input type="submit" class="post-update" value="') # SOURCE LINE 147 __M_writer(filters.decode.utf8(_("Update response"))) __M_writer(u'">\n <a href="#" class="post-cancel">') # SOURCE LINE 148 __M_writer(filters.decode.utf8(_("Cancel"))) __M_writer(u'</a>\n </div>\n</script>\n\n<script type="text/template" id="response-comment-show-template">\n <a id="a') # SOURCE LINE 153 __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'"></a>\n <div id="comment_') # SOURCE LINE 154 __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'">\n <div class="response-body">') # SOURCE LINE 155 __M_writer(filters.decode.utf8('<%- body %>')) __M_writer(u'</div>\n <div class="discussion-flag-abuse notflagged" data-role="thread-flag" data-tooltip="Report Misuse"> \n <i class="icon icon-flag"></i><span class="flag-label"></span></div> \n <p class="posted-details">–posted <span class="timeago" title="') # SOURCE LINE 158 __M_writer(filters.decode.utf8('<%- created_at %>')) __M_writer(u'">') __M_writer(filters.decode.utf8('<%- created_at %>')) __M_writer(u'</span> by\n ') # SOURCE LINE 159 __M_writer(filters.decode.utf8("<% if (obj.username) { %>")) __M_writer(u'\n <a href="') # SOURCE LINE 160 __M_writer(filters.decode.utf8('<%- user_url %>')) __M_writer(u'" class="profile-link">') __M_writer(filters.decode.utf8('<%- username %>')) __M_writer(u'</a>\n ') # SOURCE LINE 161 __M_writer(filters.decode.utf8("<% } else {print('anonymous');} %>")) __M_writer(u'\n </p>\n <div class="discussion-response') # SOURCE LINE 163 __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'"></div>\n <div style="padding-left:20px;">\n <ul class="moderator-actions response-local">\n <li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> ') # SOURCE LINE 166 __M_writer(filters.decode.utf8(_("Edit"))) __M_writer(u'</a></li>\n <li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> ') # SOURCE LINE 167 __M_writer(filters.decode.utf8(_("Delete"))) __M_writer(u'</a></li>\n <!--<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> ') # SOURCE LINE 168 __M_writer(filters.decode.utf8(_("Close"))) __M_writer(u'</a></li>\n -->\n </ul>\n </div>\n </div>\n</script>\n\n<script type="text/template" id="thread-list-item-template">\n <a href="') # SOURCE LINE 176 __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'" data-id="') __M_writer(filters.decode.utf8('<%- id %>')) __M_writer(u'">\n <span class="title">') # SOURCE LINE 177 __M_writer(filters.decode.utf8("<%- title %>")) __M_writer(u'</span>\n ') # SOURCE LINE 178 __M_writer(filters.decode.utf8("<% if (unread_comments_count > 0) { %>")) __M_writer(u'\n <span class="comments-count unread" data-tooltip="') # SOURCE LINE 179 __M_writer(filters.decode.utf8("<%- unread_comments_count %>")) __M_writer(u' new comment') __M_writer(filters.decode.utf8("<%- unread_comments_count > 1 ? 's' : '' %>")) __M_writer(u'">') __M_writer(filters.decode.utf8("<%- comments_count %>")) __M_writer(u'</span>\n ') # SOURCE LINE 180 __M_writer(filters.decode.utf8("<% } else { %>")) __M_writer(u'\n <span class="comments-count">') # SOURCE LINE 181 __M_writer(filters.decode.utf8("<%- comments_count %>")) __M_writer(u'</span>\n ') # SOURCE LINE 182 __M_writer(filters.decode.utf8("<% } %>")) __M_writer(u'\n <span class="votes-count">+') # SOURCE LINE 183 __M_writer(filters.decode.utf8("<%- votes['up_count'] %>")) __M_writer(u'</span>\n </a>\n</script>\n<script type="text/template" id="discussion-home">\n <style>\n [class^="icon-"], [class*=" icon-"] {font-family: "FontAwesome" !important;}\n </style>\n <div class="discussion-article blank-slate">\n <section class="home-header">\n <span class="label">DISCUSSION HOME:</span>\n') # SOURCE LINE 193 if course and course.display_name_with_default: # SOURCE LINE 194 __M_writer(u' <h1 class="home-title">') __M_writer(filters.decode.utf8(course.display_name_with_default)) __M_writer(u'</h1>\n </section>\n \n') # SOURCE LINE 197 if settings.MITX_FEATURES.get('ENABLE_DISCUSSION_HOME_PANEL'): # SOURCE LINE 198 __M_writer(u' <span class="label label-settings">HOW TO USE PEPPER DISCUSSIONS</span>\n <table class="home-helpgrid">\n <tr class="helpgrid-row helpgrid-row-navigation">\n <td class="row-title">Find discussions</td>\n <td class="row-item">\n <i class="icon icon-reorder"></i>\n <span class="row-description">Focus in on specific topics</span>\n </td>\n <td class="row-item">\n <i class="icon icon-search"></i>\n <span class="row-description">Search for specific topics </span>\n </td>\n <td class="row-item">\n <i class="icon icon-sort"></i>\n <span class="row-description">Sort by date, vote, or comments </span>\n </td>\n </tr>\n <tr class="helpgrid-row helpgrid-row-participation">\n <td class="row-title">Engage with topics</td>\n <td class="row-item">\n <i class="icon icon-plus"></i>\n <span class="row-description">Upvote topics and good responses</span>\n </td>\n <td class="row-item">\n <i class="icon icon-flag"></i>\n <span class="row-description">Report Forum Misuse</span>\n </td>\n <td class="row-item">\n <i class="icon icon-star"></i>\n <span class="row-description">Follow topics for updates</span>\n </td>\n </tr>\n <!--\n <tr class="helpgrid-row helpgrid-row-notification">\n <td class="row-title">Receive updates</td>\n <td class="row-item-full" colspan="3">\n <input type="checkbox" class="email-setting" name="email-notification"></input>\n <i class="icon icon-envelope"></i>\n <span class="row-description"> If enabled, you will receive an email digest once a day notifying you about new, unread activity from posts you are following. </span>\n </td>\n </tr>\n -->\n </table>\n <!--20151124 add new information-->\n <!--begin-->\n </br>\n <table class="home-helpgrid">\n <tr>\n <td style="background-color:#b0e0e6;">\n <p style="padding:8px;">Let\'s use the discussion forum to coach and collaborate with your fellow Pepper</br>educators. <b>If you have a support issue, please contact us at </b><a href="mailto:[email protected]" style="font-color:#1ea9dc;">[email protected]</a>. Support-related items will be removed from the board.</p>\n </td>\n </tr>\n </table>\n <!--end-->\n') # SOURCE LINE 254 __M_writer(u'\n </div> \n</script>\n') return '' finally: context.caller_stack._pop_frame()
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) # 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 error: if error.status_code == 404: raise Http404 raise # 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): raise Http404 # 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': user_info, 'can_create_comment': has_permission(request.user, "create_comment", course.id), 'can_create_subcomment': has_permission(request.user, "create_sub_comment", course.id), 'can_create_thread': has_permission(request.user, "create_thread", course.id), 'annotated_content_info': 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': threads, 'roles': 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': bool( 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': course_settings, 'disable_courseware_js': True, 'uses_pattern_library': True, } return render_to_response('discussion/discussion_board.html', context)
def get_threads(request, course, user_info, discussion_id=None, per_page=THREADS_PER_PAGE): """ This may raise an appropriate subclass of cc.utils.CommentClientError if something goes wrong, or ValueError if the group_id is invalid. Arguments: request (WSGIRequest): The user request. course (CourseDescriptorWithMixins): The course object. user_info (dict): The comment client User object as a dict. discussion_id (unicode): Optional discussion id/commentable id for context. per_page (int): Optional number of threads per page. Returns: (tuple of list, dict): A tuple of the list of threads and a dict of the query parameters used for the search. """ default_query_params = { 'page': 1, 'per_page': per_page, 'sort_key': 'activity', 'text': '', 'course_id': unicode(course.id), 'user_id': request.user.id, 'context': ThreadContext.COURSE, '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 # Use the discussion id/commentable id to determine the context we are going to pass through to the backend. if get_team(discussion_id) is not None: default_query_params['context'] = ThreadContext.STANDALONE if not request.GET.get('sort_key'): # If the user did not select a sort key, use their last used sort key default_query_params['sort_key'] = user_info.get( 'default_sort_key') or default_query_params['sort_key'] elif request.GET.get('sort_key') != user_info.get('default_sort_key'): # 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 if discussion_id: is_cohorted = is_commentable_divided(course.id, discussion_id) else: is_cohorted = is_course_cohorted(course.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 = default_query_params.copy() query_params.update( strip_none( extract(request.GET, [ 'page', 'sort_key', 'text', 'commentable_ids', 'flagged', 'unread', 'unanswered', ]))) if not is_cohorted: query_params.pop('group_id', None) paginated_results = cc.Thread.search(query_params) threads = paginated_results.collection # If not provided with a discussion id, filter threads by commentable ids # which are accessible to the current user. if discussion_id is None: discussion_category_ids = set( utils.get_discussion_categories_ids(course, request.user)) threads = [ thread for thread in threads if thread.get('commentable_id') in discussion_category_ids ] for thread in threads: # patch for backward compatibility to comments service if 'pinned' not in thread: thread['pinned'] = False query_params['page'] = paginated_results.page query_params['num_pages'] = paginated_results.num_pages query_params['corrected_text'] = paginated_results.corrected_text return threads, query_params
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 = get_course_discussion_settings(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
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) })
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
def followed_threads(request, course_key, user_id): """ Ajax-only endpoint retrieving the threads followed by a specific user. """ course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) try: profiled_user = cc.User(id=user_id, course_id=course_key) query_params = { 'page': 1, 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities 'sort_key': 'date', } query_params.update( strip_none( extract( request.GET, [ 'page', 'sort_key', 'flagged', 'unread', 'unanswered', ] ) ) ) try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseServerError("Invalid group_id") if group_id is not None: query_params['group_id'] = group_id paginated_results = profiled_user.subscribed_threads(query_params) print "\n \n \n paginated results \n \n \n " print paginated_results query_params['page'] = paginated_results.page query_params['num_pages'] = paginated_results.num_pages user_info = cc.User.from_django_user(request.user).to_dict() with function_trace("get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads( course_key, paginated_results.collection, request.user, user_info ) if request.is_ajax(): is_staff = has_permission(request.user, 'openclose_thread', course.id) return utils.JsonResponse({ 'annotated_content_info': annotated_content_info, 'discussion_data': [ utils.prepare_content(thread, course_key, is_staff) for thread in paginated_results.collection ], 'page': query_params['page'], 'num_pages': query_params['num_pages'], }) #TODO remove non-AJAX support, it does not appear to be used and does not appear to work. else: context = { 'course': course, 'user': request.user, 'django_user': User.objects.get(id=user_id), 'profiled_user': profiled_user.to_dict(), 'threads': paginated_results.collection, 'user_info': user_info, 'annotated_content_info': annotated_content_info, # 'content': content, } return render_to_response('discussion/user_profile.html', context) except User.DoesNotExist: raise Http404
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 newrelic_function_trace("get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads( course_key, threads, user, user_info) with newrelic_function_trace("add_courseware_context"): add_courseware_context(threads, course, user) with newrelic_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), # TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts 'upgrade_link': check_and_get_upgrade_link(request, user, course.id), 'upgrade_price': get_cosmetic_verified_display_price(course), # ENDTODO # 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), }) return context
def render_body(context, **pageargs): __M_caller = context.caller_stack._push_frame() try: __M_locals = __M_dict_builtin(pageargs=pageargs) can_create_subcomment = context.get('can_create_subcomment', UNDEFINED) sort_preference = context.get('sort_preference', UNDEFINED) course = context.get('course', UNDEFINED) getattr = context.get('getattr', UNDEFINED) can_create_comment = context.get('can_create_comment', UNDEFINED) flag_moderator = context.get('flag_moderator', UNDEFINED) user_group_id = context.get('user_group_id', UNDEFINED) course_expiration_fragment = context.get('course_expiration_fragment', UNDEFINED) user = context.get('user', UNDEFINED) __M_writer = context.writer() __M_writer(u'\n') __M_writer(u'\n\n') __M_writer(u'\n\n') __M_writer( u'\n\n<section class="discussion discussion-board page-content-container" id="discussion-container"\n data-course-id="' ) __M_writer(filters.html_escape(filters.decode.utf8(course.id))) __M_writer(u'"\n data-user-create-comment="') __M_writer( filters.html_escape( filters.decode.utf8(json.dumps(can_create_comment)))) __M_writer(u'"\n data-user-create-subcomment="') __M_writer( filters.html_escape( filters.decode.utf8(json.dumps(can_create_subcomment)))) __M_writer( u'"\n data-read-only="false"\n data-sort-preference="' ) __M_writer(filters.html_escape(filters.decode.utf8(sort_preference))) __M_writer(u'"\n data-flag-moderator="') __M_writer( filters.html_escape(filters.decode.utf8( json.dumps(flag_moderator)))) __M_writer(u'"\n data-user-group-id="') __M_writer(filters.html_escape(filters.decode.utf8(user_group_id))) __M_writer(u'">\n <header class="page-header has-secondary">\n') __M_writer( u' <div class="page-header-main">\n <nav aria-label="' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Discussions')))) __M_writer( u'" class="sr-is-focusable" tabindex="-1">\n <div class="has-breadcrumbs"></div>\n </nav>\n </div>\n <div class="page-header-secondary">\n' ) if has_permission(user, 'create_thread', course.id): __M_writer( u' <div class="forum-actions">\n <button class="btn btn-outline-primary btn-small new-post-btn">' ) __M_writer( filters.html_escape(filters.decode.utf8(_("Add a Post")))) __M_writer(u'</button>\n </div>\n') __M_writer( u' <div class="forum-search"></div>\n </div>\n </header>\n' ) if course_expiration_fragment: __M_writer(u' ') __M_writer( filters.html_escape( filters.decode.utf8( HTML(course_expiration_fragment.content)))) __M_writer(u'\n') __M_writer(u' <div class="page-content"\n') if getattr(course, 'language'): __M_writer(u' lang="') __M_writer( filters.html_escape(filters.decode.utf8(course.language))) __M_writer(u'"\n') __M_writer( u' >\n <div class="discussion-body">\n <main id="main" class="discussion-column" aria-label="Content" tabindex="-1">\n <article class="new-post-article is-hidden" style="display: none" tabindex="-1" aria-label="' ) __M_writer( filters.html_escape(filters.decode.utf8(_("New topic form")))) __M_writer( u'"></article>\n <div class="forum-content"></div>\n </main>\n <aside class="forum-nav" role="complementary" aria-label="' ) __M_writer( filters.html_escape( filters.decode.utf8(_("Discussion thread list")))) __M_writer(u'">\n ') runtime._include_file(context, u'_filter_dropdown.html', _template_uri) __M_writer( u'\n <div class="discussion-thread-list-container"></div>\n </aside>\n </div>\n </div>\n</section>\n\n' ) runtime._include_file(context, u'_underscore_templates.html', _template_uri) __M_writer(u'\n') runtime._include_file(context, u'_thread_list_template.html', _template_uri) __M_writer(u'\n') return '' finally: context.caller_stack._pop_frame()
def forum_form_discussion(request, course_key): """ Renders the main Discussion page, potentially filtered by a search query """ 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) user = cc.User.from_django_user(request.user) user_info = user.to_dict() try: unsafethreads, query_params = get_threads( request, course, user_info) # This might process a search query is_staff = has_permission(request.user, 'openclose_thread', course.id) threads = [ utils.prepare_content(thread, course_key, is_staff) for thread in unsafethreads ] except cc.utils.CommentClientMaintenanceError: log.warning("Forum is in maintenance mode") return render_to_response('discussion/maintenance.html', { 'disable_courseware_js': True, 'uses_pattern_library': True, }) 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) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course, request.user) if request.is_ajax(): return utils.JsonResponse({ 'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads' 'annotated_content_info': annotated_content_info, 'num_pages': query_params['num_pages'], 'page': query_params['page'], 'corrected_text': query_params['corrected_text'], }) else: with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): user_cohort_id = get_cohort_id(request.user, course_key) context = { 'csrf': csrf(request)['csrf_token'], 'course': course, #'recent_active_threads': recent_active_threads, 'staff_access': bool(has_access(request.user, 'staff', course)), 'threads': threads, 'thread_pages': query_params['num_pages'], 'user_info': user_info, 'can_create_comment': has_permission(request.user, "create_comment", course.id), 'can_create_subcomment': has_permission(request.user, "create_sub_comment", course.id), 'can_create_thread': has_permission(request.user, "create_thread", course.id), 'flag_moderator': bool( has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, 'staff', course)), 'annotated_content_info': annotated_content_info, 'course_id': course.id.to_deprecated_string(), 'roles': utils.get_role_ids(course_key), 'is_moderator': has_permission(request.user, "see_all_cohorts", course_key), 'cohorts': course_settings[ "cohorts"], # still needed to render _thread_list_template 'user_cohort': user_cohort_id, # read from container in NewPostView 'is_course_cohorted': is_course_cohorted( course_key), # still needed to render _thread_list_template 'sort_preference': user.default_sort_key, 'category_map': course_settings["category_map"], 'course_settings': course_settings, 'disable_courseware_js': True, 'uses_pattern_library': True, } # print "start rendering.." return render_to_response('discussion/discussion_board.html', context)
def single_thread(request, course_key, discussion_id, thread_id): """ Renders a response to display a single discussion thread. This could either be a page refresh after navigating to a single thread, a direct link to a single thread, or an AJAX call from the discussions UI loading the responses/comments for a single thread. Depending on the HTTP headers, we'll adjust our response accordingly. """ 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) is_staff = has_permission(request.user, 'openclose_thread', course.id) 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 as error: if error.status_code == 404: raise Http404 raise # 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): raise Http404 # 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 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: # 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()] 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': user_info, 'can_create_comment': has_permission(request.user, "create_comment", course.id), 'can_create_subcomment': has_permission(request.user, "create_sub_comment", course.id), 'can_create_thread': has_permission(request.user, "create_thread", course.id), 'annotated_content_info': 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': threads, 'roles': utils.get_role_ids(course_key), 'is_moderator': is_moderator, 'thread_pages': 1, 'is_course_cohorted': is_course_cohorted(course_key), 'flag_moderator': bool( 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': course_settings, 'disable_courseware_js': True, 'uses_pattern_library': True, } return render_to_response('discussion/discussion_board.html', context)
def followed_threads(request, course_key, user_id): """ Ajax-only endpoint retrieving the threads followed by a specific user. """ # nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, "load", course_key, check_if_enrolled=True) try: profiled_user = cc.User(id=user_id, course_id=course_key) default_query_params = { "page": 1, "per_page": THREADS_PER_PAGE, # more than threads_per_page to show more activities "sort_key": "date", "sort_order": "desc", } query_params = merge_dict( default_query_params, strip_none(extract(request.GET, ["page", "sort_key", "sort_order", "flagged", "unread", "unanswered"])), ) try: group_id = get_group_id_for_comments_service(request, course_key) except ValueError: return HttpResponseBadRequest("Invalid group_id") if group_id is not None: query_params["group_id"] = group_id threads, page, num_pages = profiled_user.subscribed_threads(query_params) query_params["page"] = page query_params["num_pages"] = num_pages user_info = cc.User.from_django_user(request.user).to_dict() # 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) if request.is_ajax(): is_staff = has_permission(request.user, "openclose_thread", course.id) return utils.JsonResponse( { "annotated_content_info": annotated_content_info, "discussion_data": [utils.prepare_content(thread, course_key, is_staff) for thread in threads], "page": query_params["page"], "num_pages": query_params["num_pages"], } ) # TODO remove non-AJAX support, it does not appear to be used and does not appear to work. else: context = { "course": course, "user": request.user, "django_user": User.objects.get(id=user_id), "profiled_user": profiled_user.to_dict(), "threads": _attr_safe_json(threads), "user_info": _attr_safe_json(user_info), "annotated_content_info": _attr_safe_json(annotated_content_info), # 'content': content, } return render_to_response("discussion/user_profile.html", context) except User.DoesNotExist: raise Http404