def get_group_id_for_comments_service(request, course_key, commentable_id=None): """ Given a user requesting content within a `commentable_id`, determine the group_id which should be passed to the comments service. Returns: int: the group_id to pass to the comments service or None if nothing should be passed Raises: ValueError if the requested group_id is invalid """ if commentable_id is None or is_commentable_cohorted(course_key, commentable_id): if request.method == "GET": requested_group_id = request.GET.get('group_id') elif request.method == "POST": requested_group_id = request.POST.get('group_id') if cached_has_permission(request.user, "see_all_cohorts", course_key): if not requested_group_id: return None try: group_id = int(requested_group_id) get_cohort_by_id(course_key, group_id) except CourseUserGroup.DoesNotExist: raise ValueError else: # regular users always query with their own id. group_id = get_cohort_id(request.user, course_key) return group_id else: # Never pass a group_id to the comments service for a non-cohorted # commentable return None
def prepare_content(content, course_key, is_staff=False): """ 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. """ 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', ] 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 {0} in endorsement for comment {1} but not in our DB.".format( 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 cached_has_permission(endorser, "endorse_comment", course_id)) ): endorsement["username"] = endorser.username else: del endorsement["user_id"] 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) for child in content[child_content_key] ] content[child_content_key] = children # 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 return content
def test_delete_empty_cohort(self): """ Make sure that cohorts.delete_empty_cohort() properly removes an empty cohort for a given course. """ course = modulestore().get_course(self.toy_course_key) user = UserFactory(username="******", email="*****@*****.**") empty_cohort = CohortFactory(course_id=course.id, name="EmptyCohort") nonempty_cohort = CohortFactory(course_id=course.id, name="NonemptyCohort") nonempty_cohort.users.add(user) cohorts.delete_empty_cohort(course.id, "EmptyCohort") # Make sure we cannot access the deleted cohort self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.get_cohort_by_id(course.id, empty_cohort.id) ) self.assertRaises( ValueError, lambda: cohorts.delete_empty_cohort(course.id, "NonemptyCohort") ) self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.delete_empty_cohort(SlashSeparatedCourseKey('course', 'does_not', 'exist'), "EmptyCohort") ) self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.delete_empty_cohort(course.id, "NonExistentCohort") )
def test_get_cohort_by_id(self): """ Make sure cohorts.get_cohort_by_id() properly finds a cohort by id for a given course. """ course = modulestore().get_course(self.toy_course_key) cohort = CohortFactory(course_id=course.id, name="MyCohort") self.assertEqual(cohorts.get_cohort_by_id(course.id, cohort.id), cohort) cohort.delete() self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.get_cohort_by_id(course.id, cohort.id))
def test_get_cohort_by_id(self): """ Make sure cohorts.get_cohort_by_id() properly finds a cohort by id for a given course. """ course = modulestore().get_course(self.toy_course_key) cohort = CohortFactory(course_id=course.id, name="MyCohort") self.assertEqual(cohorts.get_cohort_by_id(course.id, cohort.id), cohort) cohort.delete() self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.get_cohort_by_id(course.id, cohort.id) )
def test_delete_empty_cohort(self): """ Make sure that cohorts.delete_empty_cohort() properly removes an empty cohort for a given course. """ course = modulestore().get_course(self.toy_course_key) user = UserFactory(username="******", email="*****@*****.**") empty_cohort = CohortFactory(course_id=course.id, name="EmptyCohort") nonempty_cohort = CohortFactory(course_id=course.id, name="NonemptyCohort") nonempty_cohort.users.add(user) cohorts.delete_empty_cohort(course.id, "EmptyCohort") # Make sure we cannot access the deleted cohort self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.get_cohort_by_id(course.id, empty_cohort.id)) self.assertRaises( ValueError, lambda: cohorts.delete_empty_cohort(course.id, "NonemptyCohort")) self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.delete_empty_cohort( SlashSeparatedCourseKey('course', 'does_not', 'exist'), "EmptyCohort")) self.assertRaises( CourseUserGroup.DoesNotExist, lambda: cohorts.delete_empty_cohort( course.id, "NonExistentCohort"))
def add_thread_group_name(thread_info, course_key): """ Augment the specified thread info to include the group name if a group id is present. """ if thread_info.get('group_id') is not None: thread_info['group_name'] = get_cohort_by_id( course_key, thread_info.get('group_id')).name
def create_thread(request, course_id, commentable_id): """ Given a course and commentble ID, create the thread """ log.debug("Creating new thread in %r, id %r", course_id, commentable_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, "load", course_key) post = request.POST if course.allow_anonymous: anonymous = post.get("anonymous", "false").lower() == "true" else: anonymous = False if course.allow_anonymous_to_peers: anonymous_to_peers = post.get("anonymous_to_peers", "false").lower() == "true" else: anonymous_to_peers = False if "title" not in post or not post["title"].strip(): return JsonError(_("Title can't be empty")) if "body" not in post or not post["body"].strip(): return JsonError(_("Body can't be empty")) thread = cc.Thread( anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, commentable_id=commentable_id, course_id=course_key.to_deprecated_string(), user_id=request.user.id, thread_type=post["thread_type"], body=post["body"], title=post["title"], ) user = cc.User.from_django_user(request.user) # kevinchugh because the new requirement is that all groups will be determined # by the group id in the request this all goes away # not anymore, only for admins # Cohort the thread if the commentable is cohorted. if is_commentable_cohorted(course_key, commentable_id): user_group_id = get_cohort_id(user, course_key) # TODO (vshnayder): once we have more than just cohorts, we'll want to # change this to a single get_group_for_user_and_commentable function # that can do different things depending on the commentable_id if cached_has_permission(request.user, "see_all_cohorts", course_key): # admins can optionally choose what group to post as try: group_id = int(post.get("group_id", user_group_id)) get_cohort_by_id(course_key, group_id) except (ValueError, CourseUserGroup.DoesNotExist): return HttpResponseBadRequest("Invalid cohort id") else: # regular users always post with their own id. group_id = user_group_id if group_id: thread.group_id = group_id thread.save() # patch for backward compatibility to comments service if not "pinned" in thread.attributes: thread["pinned"] = False if post.get("auto_subscribe", "false").lower() == "true": user = cc.User.from_django_user(request.user) user.follow(thread) data = thread.to_dict() add_thread_group_name(data, course_key) add_courseware_context([data], course) if request.is_ajax(): return ajax_content_response(request, course_key, data) else: return JsonResponse(safe_content(data, course_key))
def add_thread_group_name(thread_info, course_key): """ Augment the specified thread info to include the group name if a group id is present. """ if thread_info.get('group_id') is not None: thread_info['group_name'] = get_cohort_by_id(course_key, thread_info.get('group_id')).name
def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE): """ This may raise an appropriate subclass of cc.utils.CommentClientError if something goes wrong. """ default_query_params = { 'page': 1, 'per_page': per_page, 'sort_key': 'date', 'sort_order': 'desc', 'text': '', 'commentable_id': discussion_id, 'course_id': course_id, 'user_id': request.user.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, othewrise, if mod, show all, else if student, use cohort group_id = request.GET.get('group_id') if group_id == "all": group_id = None if not group_id: if not cached_has_permission(request.user, "see_all_cohorts", course_id): group_id = get_cohort_id(request.user, course_id) if 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']))) threads, page, num_pages = cc.Thread.search(query_params) #now add the group name if the thread has a group id for thread in threads: if thread.get('group_id'): thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name thread['group_string'] = "This post visible only to Group %s." % (thread['group_name']) else: thread['group_name'] = "" thread['group_string'] = "This post visible to everyone." #patch for backward compatibility to comments service if not 'pinned' in thread: thread['pinned'] = False query_params['page'] = page query_params['num_pages'] = num_pages return threads, query_params
def single_thread(request, course_id, discussion_id, thread_id): nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, course_id, 'load_forum') cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() # 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. 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") ) if request.is_ajax(): with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"): annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info) content = utils.safe_content(thread.to_dict()) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context([content], course) return utils.JsonResponse({ 'content': content, 'annotated_content_info': annotated_content_info, }) else: with newrelic.agent.FunctionTrace(nr_transaction, "get_discussion_category_map"): category_map = utils.get_discussion_category_map(course) threads, query_params = get_threads(request, course_id) threads.append(thread.to_dict()) course = get_course_with_access(request.user, course_id, 'load_forum') with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course) for thread in threads: if thread.get('group_id') and not thread.get('group_name'): thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name #patch for backward compatibility with comments service if not "pinned" in thread: thread["pinned"] = False threads = [utils.safe_content(thread) for thread in threads] #recent_active_threads = cc.search_recent_active_threads( # course_id, # recursive=False, # query_params={'follower_id': request.user.id}, #) with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): cohorts = get_course_cohorts(course_id) cohorted_commentables = get_cohorted_commentables(course_id) user_cohort = get_cohort_id(request.user, course_id) context = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], 'init': '', # TODO: What is this? 'user_info': saxutils.escape(json.dumps(user_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course': course, #'recent_active_threads': recent_active_threads, 'course_id': course.id, # TODO: Why pass both course and course.id to template? 'thread_id': thread_id, 'threads': saxutils.escape(json.dumps(threads), escapedict), 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), 'thread_pages': query_params['num_pages'], 'is_course_cohorted': is_course_cohorted(course_id), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), 'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, course, 'staff'), 'cohorts': cohorts, 'user_cohort': get_cohort_id(request.user, course_id), 'cohorted_commentables': cohorted_commentables } return render_to_response('discussion/index.html', context)
def single_thread(request, course_id, discussion_id, thread_id): nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, course_id, 'load_forum') cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id) if request.is_ajax(): with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"): annotated_content_info = utils.get_annotated_content_infos( course_id, thread, request.user, user_info=user_info) context = {'thread': thread.to_dict(), 'course_id': course_id} # TODO: Remove completely or switch back to server side rendering # html = render_to_string('discussion/_ajax_single_thread.html', context) content = utils.safe_content(thread.to_dict()) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context([content], course) return utils.JsonResponse({ #'html': html, 'content': content, 'annotated_content_info': annotated_content_info, }) else: with newrelic.agent.FunctionTrace(nr_transaction, "get_discussion_category_map"): category_map = utils.get_discussion_category_map(course) threads, query_params = get_threads(request, course_id) threads.append(thread.to_dict()) course = get_course_with_access(request.user, course_id, 'load_forum') with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course) for thread in threads: if thread.get('group_id') and not thread.get('group_name'): thread['group_name'] = get_cohort_by_id( course_id, thread.get('group_id')).name #patch for backward compatibility with comments service if not "pinned" in thread: thread["pinned"] = False threads = [utils.safe_content(thread) for thread in threads] #recent_active_threads = cc.search_recent_active_threads( # course_id, # recursive=False, # query_params={'follower_id': request.user.id}, #) #trending_tags = cc.search_trending_tags( # course_id, #) with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads( course_id, threads, request.user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): cohorts = get_course_cohorts(course_id) cohorted_commentables = get_cohorted_commentables(course_id) user_cohort = get_cohort_id(request.user, course_id) context = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], 'init': '', # TODO: What is this? 'user_info': saxutils.escape(json.dumps(user_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course': course, #'recent_active_threads': recent_active_threads, #'trending_tags': trending_tags, 'course_id': course.id, # TODO: Why pass both course and course.id to template? 'thread_id': thread_id, 'threads': saxutils.escape(json.dumps(threads), escapedict), 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), 'thread_pages': query_params['num_pages'], 'is_course_cohorted': is_course_cohorted(course_id), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), 'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, course, 'staff'), 'cohorts': cohorts, 'user_cohort': get_cohort_id(request.user, course_id), 'cohorted_commentables': cohorted_commentables } return render_to_response('discussion/single_thread.html', context)
def single_thread(request, course_id, discussion_id, thread_id): nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, course_id, 'load_forum') cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() # 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 if request.is_ajax(): with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"): annotated_content_info = utils.get_annotated_content_infos( course_id, thread, request.user, user_info=user_info) content = utils.safe_content(thread.to_dict()) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context([content], course) return utils.JsonResponse({ 'content': content, 'annotated_content_info': annotated_content_info, }) else: with newrelic.agent.FunctionTrace(nr_transaction, "get_discussion_category_map"): category_map = utils.get_discussion_category_map(course) threads, query_params = get_threads(request, course_id) threads.append(thread.to_dict()) course = get_course_with_access(request.user, course_id, 'load_forum') with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course) for thread in threads: if thread.get('group_id') and not thread.get('group_name'): thread['group_name'] = get_cohort_by_id( course_id, thread.get('group_id')).name #patch for backward compatibility with comments service if not "pinned" in thread: thread["pinned"] = False threads = [utils.safe_content(thread) for thread in threads] with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads( course_id, threads, request.user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): cohorts = get_course_cohorts(course_id) cohorted_commentables = get_cohorted_commentables(course_id) user_cohort = get_cohort_id(request.user, course_id) context = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], 'init': '', # TODO: What is this? 'user_info': saxutils.escape(json.dumps(user_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course': course, #'recent_active_threads': recent_active_threads, 'course_id': course.id, # TODO: Why pass both course and course.id to template? 'thread_id': thread_id, 'threads': saxutils.escape(json.dumps(threads), escapedict), 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), 'thread_pages': query_params['num_pages'], 'is_course_cohorted': is_course_cohorted(course_id), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), 'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, course, 'staff'), 'cohorts': cohorts, 'user_cohort': get_cohort_id(request.user, course_id), 'cohorted_commentables': cohorted_commentables } return render_to_response('discussion/index.html', context)
def single_thread(request, course_id, discussion_id, thread_id): nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, course_id, 'load_forum') cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() try: thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id) except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError): log.error("Error loading single thread.") raise if request.is_ajax(): with newrelic.agent.FunctionTrace(nr_transaction, "get_courseware_context"): courseware_context = get_courseware_context(thread, course) with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"): annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info) context = {'thread': thread.to_dict(), 'course_id': course_id} # TODO: Remove completely or switch back to server side rendering # html = render_to_string('discussion/_ajax_single_thread.html', context) content = utils.safe_content(thread.to_dict()) if courseware_context: content.update(courseware_context) return utils.JsonResponse({ #'html': html, 'content': content, 'annotated_content_info': annotated_content_info, }) else: with newrelic.agent.FunctionTrace(nr_transaction, "get_discussion_category_map"): category_map = utils.get_discussion_category_map(course) try: threads, query_params = get_threads(request, course_id) threads.append(thread.to_dict()) except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError): log.error("Error loading single thread.") raise course = get_course_with_access(request.user, course_id, 'load_forum') with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context_to_threads"): for thread in threads: courseware_context = get_courseware_context(thread, course) if courseware_context: thread.update(courseware_context) if thread.get('group_id') and not thread.get('group_name'): thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name #patch for backward compatibility with comments service if not "pinned" in thread: thread["pinned"] = False threads = [utils.safe_content(thread) for thread in threads] #recent_active_threads = cc.search_recent_active_threads( # course_id, # recursive=False, # query_params={'follower_id': request.user.id}, #) #trending_tags = cc.search_trending_tags( # course_id, #) with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): cohorts = get_course_cohorts(course_id) cohorted_commentables = get_cohorted_commentables(course_id) user_cohort = get_cohort_id(request.user, course_id) context = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], 'init': '', # TODO: What is this? 'user_info': saxutils.escape(json.dumps(user_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course': course, #'recent_active_threads': recent_active_threads, #'trending_tags': trending_tags, 'course_id': course.id, # TODO: Why pass both course and course.id to template? 'thread_id': thread_id, 'threads': saxutils.escape(json.dumps(threads), escapedict), 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), 'thread_pages': query_params['num_pages'], 'is_course_cohorted': is_course_cohorted(course_id), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), 'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, course, 'staff'), 'cohorts': cohorts, 'user_cohort': get_cohort_id(request.user, course_id), 'cohorted_commentables': cohorted_commentables } return render_to_response('discussion/single_thread.html', context)
def single_thread(request, course_id, discussion_id, thread_id): course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) nr_transaction = newrelic.agent.current_transaction() course = get_course_with_access(request.user, 'load_forum', course_id) course_settings = make_course_settings(course, include_category_map=True) cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() # 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 is_staff = cached_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_id, thread, request.user, user_info=user_info) content = utils.safe_content(thread.to_dict(), is_staff) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context([content], course) return utils.JsonResponse({ 'content': content, 'annotated_content_info': annotated_content_info, }) else: threads, query_params = get_threads(request, course_id) threads.append(thread.to_dict()) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context(threads, course) for thread in threads: if thread.get('group_id') and not thread.get('group_name'): thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name #patch for backward compatibility with comments service if not "pinned" in thread: thread["pinned"] = False threads = [utils.safe_content(thread, 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_id, threads, request.user, user_info) with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"): user_cohort = get_cohort_id(request.user, course_id) context = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], 'init': '', # TODO: What is this? 'user_info': _attr_safe_json(user_info), 'annotated_content_info': _attr_safe_json(annotated_content_info), 'course': course, #'recent_active_threads': recent_active_threads, 'course_id': course.id.to_deprecated_string(), # TODO: Why pass both course and course.id to template? 'thread_id': thread_id, 'threads': _attr_safe_json(threads), 'roles': _attr_safe_json(utils.get_role_ids(course_id)), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), 'thread_pages': query_params['num_pages'], 'is_course_cohorted': is_course_cohorted(course_id), 'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, 'staff', course), 'cohorts': course_settings["cohorts"], 'user_cohort': user_cohort, 'sort_preference': cc_user.default_sort_key, 'category_map': course_settings["category_map"], 'course_settings': _attr_safe_json(course_settings) } return render_to_response('discussion/index.html', context)
def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE): """ This may raise an appropriate subclass of cc.utils.CommentClientError if something goes wrong. """ default_query_params = { 'page': 1, 'per_page': per_page, 'sort_key': 'date', 'sort_order': 'desc', 'text': '', 'commentable_id': discussion_id, 'course_id': course_id, 'user_id': request.user.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, othewrise, if mod, show all, else if student, use cohort group_id = request.GET.get('group_id') if group_id == "all": group_id = None if not group_id: if not cached_has_permission(request.user, "see_all_cohorts", course_id): group_id = get_cohort_id(request.user, course_id) if 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' ]))) threads, page, num_pages = cc.Thread.search(query_params) #now add the group name if the thread has a group id for thread in threads: if thread.get('group_id'): thread['group_name'] = get_cohort_by_id( course_id, thread.get('group_id')).name thread['group_string'] = "This post visible only to Group %s." % ( thread['group_name']) else: thread['group_name'] = "" thread['group_string'] = "This post visible to everyone." #patch for backward compatibility to comments service if not 'pinned' in thread: thread['pinned'] = False query_params['page'] = page query_params['num_pages'] = num_pages return threads, query_params
def single_thread(request, course_id, discussion_id, thread_id): course = get_course_with_access(request.user, course_id, 'load_forum') cc_user = cc.User.from_django_user(request.user) user_info = cc_user.to_dict() try: thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id) except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError): log.error("Error loading single thread.") context = {'window_title':'Message', 'error_title':'', 'error_message':"This discussion has been deleted. <a href='/dashboard'>Click here</a> to go back to your dashboard. \ For additional help contact <a href='mailto:[email protected]'>[email protected]</a>."} return render_to_response('error.html', context) # raise Http404 if len(thread.get('children'))<1: create_comment_auto_load(request, course_id, thread_id,User.objects.get(id=thread.get("user_id"))) if request.is_ajax(): annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info) context = {'thread': thread.to_dict(), 'course_id': course_id} # TODO: Remove completely or switch back to server side rendering # html = render_to_string('discussion/_ajax_single_thread.html', context) content = utils.safe_content(thread.to_dict()) #courseware_context = get_courseware_context(thread, course) #if courseware_context: # content.update(courseware_context) if thread.get('tags')[0]!='portfolio': id_map = get_discussion_id_map(course) id = content['commentable_id'] content_info = None if id in id_map: location = id_map[id]["location"].url() title = id_map[id]["title"] url = reverse('jump_to', kwargs={"course_id": course.location.course_id, "location": location}) content.update({"courseware_url": url, "courseware_title": title}) return utils.JsonResponse({ #'html': html, 'content': content, 'annotated_content_info': annotated_content_info, }) else: category_map = utils.get_discussion_category_map(course) id_map = get_discussion_id_map(course) try: threads, query_params = get_threads_tags(request, course_id) threads.append(thread.to_dict()) except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError): log.error("Error loading single thread.") raise Http404 course = get_course_with_access(request.user, course_id, 'load_forum') thread_output = [] for thread in threads: #courseware_context = get_courseware_context(thread, course) #if courseware_context: # thread.update(courseware_context) id = thread['commentable_id'] content_info = None if id in id_map: location = id_map[id]["location"].url() title = id_map[id]["title"] url = reverse('jump_to', kwargs={"course_id": course.location.course_id, "location": location}) thread.update({"courseware_url": url, "courseware_title": title}) if thread.get('group_id') and not thread.get('group_name'): thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name #patch for backward compatibility with comments service if not "pinned" in thread: thread["pinned"] = False if len(thread.get('tags'))>0: #if thread.get('tags')[0]!='portfolio' and str(thread.get('courseware_url')).find('__am')<0: if thread.get('tags')[0]!='portfolio' and thread.get('tags')[0]!='aboutme': thread_output.append(thread) thread_output = [utils.safe_content(thread) for thread in thread_output] #recent_active_threads = cc.search_recent_active_threads( # course_id, # recursive=False, # query_params={'follower_id': request.user.id}, #) #trending_tags = cc.search_trending_tags( # course_id, #) annotated_content_info = utils.get_metadata_for_threads(course_id, thread_output, request.user, user_info) cohorts = get_course_cohorts(course_id) cohorted_commentables = get_cohorted_commentables(course_id) user_cohort = get_cohort_id(request.user, course_id) context = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], 'init': '', # TODO: What is this? 'user_info': saxutils.escape(json.dumps(user_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course': course, #'recent_active_threads': recent_active_threads, #'trending_tags': trending_tags, 'course_id': course.id, # TODO: Why pass both course and course.id to template? 'thread_id': thread_id, 'threads': saxutils.escape(json.dumps(thread_output), escapedict), 'category_map': category_map, 'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict), 'thread_pages': query_params['num_pages'], 'is_course_cohorted': is_course_cohorted(course_id), 'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id), 'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, course, 'staff'), 'cohorts': cohorts, 'user_cohort': get_cohort_id(request.user, course_id), 'cohorted_commentables': cohorted_commentables } return render_to_response('discussion/single_thread.html', context)