def users(request, course_id): """ Given a `username` query parameter, find matches for users in the forum for this course. Only exact matches are supported here, so the length of the result set will either be 0 or 1. """ course_key = CourseKey.from_string(course_id) try: get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) except Http404: # course didn't exist, or requesting user does not have access to it. return JsonError(status=404) try: username = request.GET['username'] except KeyError: # 400 is default status for JsonError return JsonError(["username parameter is required"]) user_objs = [] try: matched_user = User.objects.get(username=username) cc_user = cc.User.from_django_user(matched_user) cc_user.course_id = course_key cc_user.retrieve(complete=False) if (cc_user['threads_count'] + cc_user['comments_count']) > 0: user_objs.append({ 'id': matched_user.id, 'username': matched_user.username, }) except User.DoesNotExist: pass return JsonResponse({"users": user_objs})
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"] ) # Cohort the thread if required try: group_id = get_group_id_for_comments_service(request, course_key, commentable_id) except ValueError: return HttpResponseBadRequest("Invalid cohort id") if group_id is not None: 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 update_thread(request, course_id, thread_id): """ Given a course id and thread id, update a existing thread, used for both static and ajax submissions """ if 'title' not in request.POST or not request.POST['title'].strip(): return JsonError(_("Title can't be empty")) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) thread = cc.Thread.find(thread_id) thread.body = request.POST["body"] thread.title = request.POST["title"] # The following checks should avoid issues we've seen during deploys, where end users are hitting an updated server # while their browser still has the old client code. This will avoid erasing present values in those cases. if "thread_type" in request.POST: thread.thread_type = request.POST["thread_type"] if "commentable_id" in request.POST: course = get_course_with_access(request.user, 'load', course_key) if discussion_category_id_access(course, request.user, request.POST.get("commentable_id")): thread.commentable_id = request.POST["commentable_id"] else: return JsonError(_("Topic doesn't exist")) thread.save() if request.is_ajax(): return ajax_content_response(request, course_key, thread.to_dict()) else: return JsonResponse(prepare_content(thread.to_dict(), course_key))
def process_exception(self, request, exception): """ Processes CommentClientErrors in ajax requests. If the request is an ajax request, returns a http response that encodes the error as json """ if isinstance(exception, CommentClientError) and request.is_ajax(): try: return JsonError(json.loads(exception.message)) except ValueError: return JsonError(exception.message) return None
def update_thread(request, course_id, thread_id): """ Given a course id and thread id, update a existing thread, used for both static and ajax submissions """ if 'title' not in request.POST or not request.POST['title'].strip(): return JsonError(_("Title can't be empty")) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) thread = cc.Thread.find(thread_id) thread.update_attributes(**extract(request.POST, ['body', 'title'])) thread.save() if request.is_ajax(): return ajax_content_response(request, course_id, thread.to_dict()) else: return JsonResponse(utils.safe_content(thread.to_dict()))
def update_moderator_status(request, course_id, user_id): """ given a course id and user id, check if the user has moderator and send back a user profile """ is_moderator = request.POST.get('is_moderator', '').lower() if is_moderator not in ["true", "false"]: return JsonError("Must provide is_moderator as boolean value") is_moderator = is_moderator == "true" user = User.objects.get(id=user_id) role = Role.objects.get(course_id=course_id, name="Moderator") if is_moderator: user.roles.add(role) else: user.roles.remove(role) if request.is_ajax(): course = get_course_with_access(request.user, course_id, 'load') discussion_user = cc.User(id=user_id, course_id=course_id) context = { 'course': course, 'course_id': course_id, 'user': request.user, 'django_user': user, 'profiled_user': discussion_user.to_dict(), } return JsonResponse({ 'html': render_to_string('discussion/ajax_user_profile.html', context) }) else: return JsonResponse({})
def wrapper(request, *args, **kwargs): """ Wrapper for the view that only calls the view if the user is authorized. """ def fetch_content(): """ Extract the forum object from the keyword arguments to the view. """ if "thread_id" in kwargs: content = cc.Thread.find(kwargs["thread_id"]).to_dict() elif "comment_id" in kwargs: content = cc.Comment.find(kwargs["comment_id"]).to_dict() elif "commentable_id" in kwargs: content = cc.Commentable.find( kwargs["commentable_id"]).to_dict() else: content = None return content course_key = CourseKey.from_string(kwargs['course_id']) if check_permissions_by_view(request.user, course_key, fetch_content(), request.view_name): return func(request, *args, **kwargs) else: return JsonError("unauthorized", status=401)
def create_sub_comment(request, course_id, comment_id): """ given a course_id and comment_id, create a response to a comment after checking the max depth allowed, if allowed """ if is_comment_too_deep(parent=cc.Comment(comment_id)): return JsonError(_("Comment level too deep")) return _create_comment(request, CourseKey.from_string(course_id), parent_id=comment_id)
def update_thread(request, course_id, thread_id): """ Given a course id and thread id, update a existing thread, used for both static and ajax submissions """ if 'title' not in request.POST or not request.POST['title'].strip(): return JsonError(_("Title can't be empty")) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) thread = cc.Thread.find(thread_id) thread.body = request.POST["body"] thread.title = request.POST["title"] thread.save() if request.is_ajax(): return ajax_content_response(request, course_key, thread.to_dict()) else: return JsonResponse(safe_content(thread.to_dict(), course_key))
def create_comment(request, course_id, thread_id): """ given a course_id and thread_id, test for comment depth. if not too deep, call _create_comment to create the actual comment. """ if is_comment_too_deep(parent=None): return JsonError(_("Comment level too deep")) return _create_comment(request, CourseKey.from_string(course_id), thread_id=thread_id)
def create_sub_comment(request, course_id, comment_id): """ given a course_id and comment_id, create a response to a comment after checking the max depth allowed, if allowed """ if cc_settings.MAX_COMMENT_DEPTH is not None: if cc_settings.MAX_COMMENT_DEPTH <= cc.Comment.find(comment_id).depth: return JsonError(_("Comment level too deep")) return _create_comment(request, course_id, parent_id=comment_id)
def create_comment(request, course_id, thread_id): """ given a course_id and thread_id, test for comment depth. if not too deep, call _create_comment to create the actual comment. """ if cc_settings.MAX_COMMENT_DEPTH is not None: if cc_settings.MAX_COMMENT_DEPTH < 0: return JsonError(_("Comment level too deep")) return _create_comment(request, course_id, thread_id=thread_id)
def _create_comment(request, course_key, thread_id=None, parent_id=None): """ given a course_key, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ assert isinstance(course_key, CourseKey) post = request.POST if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(request.user, 'load', course_key) 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 comment = cc.Comment( anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, user_id=request.user.id, course_id=course_key.to_deprecated_string(), thread_id=thread_id, parent_id=parent_id, body=post["body"] ) comment.save() followed = post.get('auto_subscribe', 'false').lower() == 'true' if followed: user = cc.User.from_django_user(request.user) user.follow(comment.thread) event_data = {'discussion': {'id': comment.thread_id}, 'options': {'followed': followed}} if parent_id: event_data['response'] = {'id': comment.parent_id} event_name = 'edx.forum.comment.created' else: event_name = 'edx.forum.response.created' event_data['commentable_id'] = comment.thread.commentable_id track_forum_event(request, event_name, course, comment, event_data) if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course.id))
def wrapper(request, *args, **kwargs): def fetch_content(): if "thread_id" in kwargs: content = cc.Thread.find(kwargs["thread_id"]).to_dict() elif "comment_id" in kwargs: content = cc.Comment.find(kwargs["comment_id"]).to_dict() else: content = None return content if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name): return fn(request, *args, **kwargs) else: return JsonError("unauthorized", status=401)
def update_comment(request, course_id, comment_id): """ given a course_id and comment_id, update the comment with payload attributes handles static and ajax submissions """ comment = cc.Comment.find(comment_id) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) comment.update_attributes(**extract(request.POST, ['body'])) comment.save() if request.is_ajax(): return ajax_content_response(request, course_id, comment.to_dict()) else: return JsonResponse(utils.safe_content(comment.to_dict()))
def wrapper(request, *args, **kwargs): def fetch_content(): if "thread_id" in kwargs: content = cc.Thread.find(kwargs["thread_id"]).to_dict() elif "comment_id" in kwargs: content = cc.Comment.find(kwargs["comment_id"]).to_dict() else: content = None return content course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id']) if check_permissions_by_view(request.user, course_key, fetch_content(), request.view_name): return fn(request, *args, **kwargs) else: return JsonError("unauthorized", status=401)
def update_thread(request, course_id, thread_id): """ Given a course id and thread id, update a existing thread, used for both static and ajax submissions """ if 'title' not in request.POST or not request.POST['title'].strip(): return JsonError(_("Title can't be empty")) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) course_key = CourseKey.from_string(course_id) thread = cc.Thread.find(thread_id) # Get thread context first in order to be safe from reseting the values of thread object later thread_context = getattr(thread, "context", "course") thread.body = request.POST["body"] thread.title = request.POST["title"] user = request.user # The following checks should avoid issues we've seen during deploys, where end users are hitting an updated server # while their browser still has the old client code. This will avoid erasing present values in those cases. if "thread_type" in request.POST: thread.thread_type = request.POST["thread_type"] if "commentable_id" in request.POST: commentable_id = request.POST["commentable_id"] course = get_course_with_access(user, 'load', course_key) if thread_context == "course" and not discussion_category_id_access( course, user, commentable_id): return JsonError(_("Topic doesn't exist")) else: thread.commentable_id = commentable_id thread.save() thread_edited.send(sender=None, user=user, post=thread) if request.is_ajax(): return ajax_content_response(request, course_key, thread.to_dict()) else: return JsonResponse(prepare_content(thread.to_dict(), course_key))
def update_comment(request, course_id, comment_id): """ given a course_id and comment_id, update the comment with payload attributes handles static and ajax submissions """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) comment = cc.Comment.find(comment_id) if 'body' not in request.POST or not request.POST['body'].strip(): return JsonError(_("Body can't be empty")) comment.body = request.POST["body"] comment.save() if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course_key))
def _create_comment(request, course_key, thread_id=None, parent_id=None): """ given a course_key, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ assert isinstance(course_key, CourseKey) post = request.POST user = request.user if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(user, 'load', course_key) 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 comment = cc.Comment(anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, user_id=user.id, course_id=course_key.to_deprecated_string(), thread_id=thread_id, parent_id=parent_id, body=post["body"]) comment.save() comment_created.send(sender=None, user=user, post=comment) followed = post.get('auto_subscribe', 'false').lower() == 'true' if followed: cc_user = cc.User.from_django_user(request.user) cc_user.follow(comment.thread) track_comment_created_event(request, course, comment, comment.thread.commentable_id, followed) if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course.id))
def _create_comment(request, course_id, thread_id=None, parent_id=None): """ given a course_id, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ post = request.POST if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) comment = cc.Comment(**extract(post, ['body'])) course = get_course_with_access(request.user, course_id, 'load') 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 comment.update_attributes( **{ 'anonymous': anonymous, 'anonymous_to_peers': anonymous_to_peers, 'user_id': request.user.id, 'course_id': course_id, 'thread_id': thread_id, 'parent_id': parent_id, }) comment.save() if post.get('auto_subscribe', 'false').lower() == 'true': user = cc.User.from_django_user(request.user) user.follow(comment.thread) if request.is_ajax(): return ajax_content_response(request, course_id, comment.to_dict()) else: return JsonResponse(utils.safe_content(comment.to_dict()))
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 user = request.user 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")) params = { 'anonymous': anonymous, 'anonymous_to_peers': anonymous_to_peers, 'commentable_id': commentable_id, 'course_id': course_key.to_deprecated_string(), 'user_id': user.id, 'thread_type': post["thread_type"], 'body': post["body"], 'title': post["title"], } # Check for whether this commentable belongs to a team, and add the right context if get_team(commentable_id) is not None: params['context'] = ThreadContext.STANDALONE else: params['context'] = ThreadContext.COURSE thread = cc.Thread(**params) # Cohort the thread if required try: group_id = get_group_id_for_comments_service(request, course_key, commentable_id) except ValueError: return HttpResponseBadRequest("Invalid cohort id") if group_id is not None: thread.group_id = group_id thread.save() thread_created.send(sender=None, user=user, post=thread) # patch for backward compatibility to comments service if 'pinned' not in thread.attributes: thread['pinned'] = False follow = post.get('auto_subscribe', 'false').lower() == 'true' if follow: cc_user = cc.User.from_django_user(user) cc_user.follow(thread) event_data = get_thread_created_event_data(thread, follow) data = thread.to_dict() add_courseware_context([data], course, user) track_forum_event(request, THREAD_CREATED_EVENT_NAME, course, thread, event_data) if request.is_ajax(): return ajax_content_response(request, course_key, data) else: return JsonResponse(prepare_content(data, course_key))
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"]) # Cohort the thread if required try: group_id = get_group_id_for_comments_service(request, course_key, commentable_id) except ValueError: return HttpResponseBadRequest("Invalid cohort id") if group_id is not None: thread.group_id = group_id thread.save() # patch for backward compatibility to comments service if 'pinned' not in thread.attributes: thread['pinned'] = False follow = post.get('auto_subscribe', 'false').lower() == 'true' if follow: user = cc.User.from_django_user(request.user) user.follow(thread) event_data = { 'title': thread.title, 'commentable_id': commentable_id, 'options': { 'followed': follow }, 'anonymous': anonymous, 'thread_type': thread.thread_type, 'group_id': group_id, 'anonymous_to_peers': anonymous_to_peers, # There is a stated desire for an 'origin' property that will state # whether this thread was created via courseware or the forum. # However, the view does not contain that data, and including it will # likely require changes elsewhere. } data = thread.to_dict() # Calls to id map are expensive, but we need this more than once. # Prefetch it. id_map = get_discussion_id_map(course, request.user) add_courseware_context([data], course, request.user, id_map=id_map) track_forum_event(request, 'edx.forum.thread.created', course, thread, event_data, id_map=id_map) if request.is_ajax(): return ajax_content_response(request, course_key, data) else: return JsonResponse(prepare_content(data, course_key))
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 = get_course_with_access(request.user, course_id, 'load') 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(**extract(post, ['body', 'title'])) thread.update_attributes( **{ 'anonymous': anonymous, 'anonymous_to_peers': anonymous_to_peers, 'commentable_id': commentable_id, 'course_id': course_id, 'user_id': request.user.id, }) 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_id, commentable_id): user_group_id = get_cohort_id(user, course_id) # 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_id): # admins can optionally choose what group to post as group_id = post.get('group_id', user_group_id) else: # regular users always post with their own id. group_id = user_group_id if group_id: thread.update_attributes(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_courseware_context([data], course) if request.is_ajax(): return ajax_content_response(request, course_id, data) else: return JsonResponse(utils.safe_content(data))
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 = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key) post = request.POST user = request.user 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")) params = { 'anonymous': anonymous, 'anonymous_to_peers': anonymous_to_peers, 'commentable_id': commentable_id, 'course_id': course_key.to_deprecated_string(), 'user_id': user.id, 'thread_type': post["thread_type"], 'body': post["body"], 'title': post["title"], } # Check for whether this commentable belongs to a team, and add the right context if get_team(commentable_id) is not None: params['context'] = ThreadContext.STANDALONE else: params['context'] = ThreadContext.COURSE thread = cc.Thread(**params) # Divide the thread if required try: group_id = get_group_id_for_comments_service(request, course_key, commentable_id) except ValueError: return HttpResponseServerError("Invalid group id for commentable") if group_id is not None: thread.group_id = group_id thread.save() thread_created.send(sender=None, user=user, post=thread) # patch for backward compatibility to comments service if 'pinned' not in thread.attributes: thread['pinned'] = False follow = post.get('auto_subscribe', 'false').lower() == 'true' if follow: cc_user = cc.User.from_django_user(user) cc_user.follow(thread) thread_followed.send(sender=None, user=user, post=thread) data = thread.to_dict() add_courseware_context([data], course, user) track_thread_created_event(request, course, thread, follow) if thread.get('group_id'): # Send a notification message, if enabled, when anyone posts a new thread on # a cohorted/private discussion, except the poster him/herself _send_discussion_notification( 'open-edx.lms.discussions.cohorted-thread-added', unicode(course_key), thread, request.user, excerpt=_get_excerpt(thread.body), recipient_group_id=thread.get('group_id'), recipient_exclude_user_ids=[request.user.id], is_anonymous_user=anonymous or anonymous_to_peers) add_thread_group_name(data, course_key) if thread.get('group_id') and not thread.get('group_name'): thread['group_name'] = get_cohort_by_id(course_key, thread.get('group_id')).name data = thread.to_dict() if request.is_ajax(): return ajax_content_response(request, course_key, data) else: return JsonResponse(prepare_content(data, course_key))
def _create_comment(request, course_key, thread_id=None, parent_id=None): """ given a course_key, thread_id, and parent_id, create a comment, called from create_comment to do the actual creation """ assert isinstance(course_key, CourseKey) post = request.POST user = request.user if 'body' not in post or not post['body'].strip(): return JsonError(_("Body can't be empty")) course = get_course_with_access(user, 'load', course_key) 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 comment = cc.Comment(anonymous=anonymous, anonymous_to_peers=anonymous_to_peers, user_id=user.id, course_id=course_key.to_deprecated_string(), thread_id=thread_id, parent_id=parent_id, body=post["body"]) comment.save() comment_created.send(sender=None, user=user, post=comment) followed = post.get('auto_subscribe', 'false').lower() == 'true' if followed: cc_user = cc.User.from_django_user(request.user) cc_user.follow(comment.thread) track_comment_created_event(request, course, comment, comment.thread.commentable_id, followed) # # Send notification # # Feature Flag to check that notifications are enabled or not. if settings.FEATURES.get("ENABLE_NOTIFICATIONS", False): action_user_id = request.user.id is_comment = not thread_id and parent_id replying_to_id = None # keep track of who we are replying to if is_comment: # If creating a comment, then we don't have the original thread_id # so we have to get it from the parent comment = cc.Comment.find(parent_id) thread_id = comment.thread_id replying_to_id = comment.user_id thread = cc.Thread.find(thread_id) # IMPORTANT: we have to use getattr here as # otherwise the property will not get fetched # from cs_comment_service thread_user_id = int(getattr(thread, 'user_id', 0)) if not replying_to_id: # we must be creating a Reponse on a thread, # so the original poster is the author of the thread replying_to_id = thread_user_id # # IMPORTANT: We have to use getattr() here so that the # object is fully hydrated. This is a known limitation. # group_id = getattr(thread, 'group_id') if group_id: # We always send a notification to the whole cohort # when someone posts a comment, except the poster _send_discussion_notification( 'open-edx.lms.discussions.cohorted-comment-added', unicode(course_key), thread, request.user, excerpt=_get_excerpt(post["body"]), recipient_group_id=thread.get('group_id'), recipient_exclude_user_ids=[request.user.id], is_anonymous_user=anonymous or anonymous_to_peers) elif parent_id is None and action_user_id != replying_to_id: # we have to only send the notifications when # the user commenting the thread is not # the same user who created the thread # parent_id is None: publish notification only when creating the comment on # the thread not replying on the comment. When the user replied on the comment # the parent_id is not None at that time _send_discussion_notification( 'open-edx.lms.discussions.reply-to-thread', unicode(course_key), thread, request.user, excerpt=_get_excerpt(post["body"]), recipient_user_id=replying_to_id, is_anonymous_user=anonymous or anonymous_to_peers) if request.is_ajax(): return ajax_content_response(request, course_key, comment.to_dict()) else: return JsonResponse(prepare_content(comment.to_dict(), course.id))