This reverts commite4819a1b55, reversing changes made tob6ec41cae0. Now that we're ready to test again, need to revert the revert to put the filtering UI back in.
489 lines
15 KiB
Python
489 lines
15 KiB
Python
import time
|
|
import random
|
|
import os
|
|
import os.path
|
|
import logging
|
|
import urlparse
|
|
import functools
|
|
|
|
import comment_client as cc
|
|
import django_comment_client.utils as utils
|
|
import django_comment_client.settings as cc_settings
|
|
|
|
|
|
from django.core import exceptions
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.views.decorators.http import require_POST, require_GET
|
|
from django.views.decorators import csrf
|
|
from django.core.files.storage import get_storage_class
|
|
from django.utils.translation import ugettext as _
|
|
from django.contrib.auth.models import User
|
|
|
|
from mitxmako.shortcuts import render_to_response, render_to_string
|
|
from courseware.courses import get_course_with_access
|
|
from course_groups.cohorts import get_cohort_id, is_commentable_cohorted
|
|
|
|
from django_comment_client.utils import JsonResponse, JsonError, extract, get_courseware_context
|
|
|
|
from django_comment_client.permissions import check_permissions_by_view, cached_has_permission
|
|
from django_comment_client.models import Role
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def permitted(fn):
|
|
@functools.wraps(fn)
|
|
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)
|
|
return wrapper
|
|
|
|
|
|
def ajax_content_response(request, course_id, content, template_name):
|
|
context = {
|
|
'course_id': course_id,
|
|
'content': content,
|
|
}
|
|
html = render_to_string(template_name, context)
|
|
user_info = cc.User.from_django_user(request.user).to_dict()
|
|
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
|
|
return JsonResponse({
|
|
'html': html,
|
|
'content': utils.safe_content(content),
|
|
'annotated_content_info': annotated_content_info,
|
|
})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def create_thread(request, course_id, commentable_id):
|
|
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.metadata.get("allow_anonymous", True):
|
|
anonymous = post.get('anonymous', 'false').lower() == 'true'
|
|
else:
|
|
anonymous = False
|
|
|
|
if course.metadata.get("allow_anonymous_to_peers", False):
|
|
anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true'
|
|
else:
|
|
anonymous_to_peers = False
|
|
|
|
thread = cc.Thread(**extract(post, ['body', 'title', 'tags']))
|
|
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
|
|
|
|
# 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
|
|
group_id = post.get('group_id')
|
|
if group_id:
|
|
thread.update_attributes(group_id=group_id)
|
|
|
|
log.debug("Saving thread %r", thread.attributes)
|
|
thread.save()
|
|
|
|
if post.get('auto_subscribe', 'false').lower() == 'true':
|
|
user = cc.User.from_django_user(request.user)
|
|
user.follow(thread)
|
|
courseware_context = get_courseware_context(thread, course)
|
|
data = thread.to_dict()
|
|
if courseware_context:
|
|
data.update(courseware_context)
|
|
if request.is_ajax():
|
|
return ajax_content_response(request, course_id, data, 'discussion/ajax_create_thread.html')
|
|
else:
|
|
return JsonResponse(utils.safe_content(data))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def update_thread(request, course_id, thread_id):
|
|
thread = cc.Thread.find(thread_id)
|
|
thread.update_attributes(**extract(request.POST, ['body', 'title', 'tags']))
|
|
thread.save()
|
|
if request.is_ajax():
|
|
return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_update_thread.html')
|
|
else:
|
|
return JsonResponse(utils.safe_content(thread.to_dict()))
|
|
|
|
|
|
def _create_comment(request, course_id, thread_id=None, parent_id=None):
|
|
post = request.POST
|
|
comment = cc.Comment(**extract(post, ['body']))
|
|
|
|
course = get_course_with_access(request.user, course_id, 'load')
|
|
if course.metadata.get("allow_anonymous", True):
|
|
anonymous = post.get('anonymous', 'false').lower() == 'true'
|
|
else:
|
|
anonymous = False
|
|
|
|
if course.metadata.get("allow_anonymous_to_peers", False):
|
|
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(), 'discussion/ajax_create_comment.html')
|
|
else:
|
|
return JsonResponse(utils.safe_content(comment.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def create_comment(request, course_id, thread_id):
|
|
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)
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def delete_thread(request, course_id, thread_id):
|
|
thread = cc.Thread.find(thread_id)
|
|
thread.delete()
|
|
return JsonResponse(utils.safe_content(thread.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def update_comment(request, course_id, comment_id):
|
|
comment = cc.Comment.find(comment_id)
|
|
comment.update_attributes(**extract(request.POST, ['body']))
|
|
comment.save()
|
|
if request.is_ajax():
|
|
return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_update_comment.html')
|
|
else:
|
|
return JsonResponse(utils.safe_content(comment.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def endorse_comment(request, course_id, comment_id):
|
|
comment = cc.Comment.find(comment_id)
|
|
comment.endorsed = request.POST.get('endorsed', 'false').lower() == 'true'
|
|
comment.save()
|
|
return JsonResponse(utils.safe_content(comment.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def openclose_thread(request, course_id, thread_id):
|
|
thread = cc.Thread.find(thread_id)
|
|
thread.closed = request.POST.get('closed', 'false').lower() == 'true'
|
|
thread.save()
|
|
thread = thread.to_dict()
|
|
return JsonResponse({
|
|
'content': utils.safe_content(thread),
|
|
'ability': utils.get_ability(course_id, thread, request.user),
|
|
})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def create_sub_comment(request, course_id, comment_id):
|
|
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)
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def delete_comment(request, course_id, comment_id):
|
|
comment = cc.Comment.find(comment_id)
|
|
comment.delete()
|
|
return JsonResponse(utils.safe_content(comment.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def vote_for_comment(request, course_id, comment_id, value):
|
|
user = cc.User.from_django_user(request.user)
|
|
comment = cc.Comment.find(comment_id)
|
|
user.vote(comment, value)
|
|
return JsonResponse(utils.safe_content(comment.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def undo_vote_for_comment(request, course_id, comment_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
comment = cc.Comment.find(comment_id)
|
|
user.unvote(comment)
|
|
return JsonResponse(utils.safe_content(comment.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def vote_for_thread(request, course_id, thread_id, value):
|
|
user = cc.User.from_django_user(request.user)
|
|
thread = cc.Thread.find(thread_id)
|
|
user.vote(thread, value)
|
|
return JsonResponse(utils.safe_content(thread.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def undo_vote_for_thread(request, course_id, thread_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
thread = cc.Thread.find(thread_id)
|
|
user.unvote(thread)
|
|
return JsonResponse(utils.safe_content(thread.to_dict()))
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def follow_thread(request, course_id, thread_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
thread = cc.Thread.find(thread_id)
|
|
user.follow(thread)
|
|
return JsonResponse({})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def follow_commentable(request, course_id, commentable_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
commentable = cc.Commentable.find(commentable_id)
|
|
user.follow(commentable)
|
|
return JsonResponse({})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def follow_user(request, course_id, followed_user_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
followed_user = cc.User.find(followed_user_id)
|
|
user.follow(followed_user)
|
|
return JsonResponse({})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def unfollow_thread(request, course_id, thread_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
thread = cc.Thread.find(thread_id)
|
|
user.unfollow(thread)
|
|
return JsonResponse({})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def unfollow_commentable(request, course_id, commentable_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
commentable = cc.Commentable.find(commentable_id)
|
|
user.unfollow(commentable)
|
|
return JsonResponse({})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def unfollow_user(request, course_id, followed_user_id):
|
|
user = cc.User.from_django_user(request.user)
|
|
followed_user = cc.User.find(followed_user_id)
|
|
user.unfollow(followed_user)
|
|
return JsonResponse({})
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@permitted
|
|
def update_moderator_status(request, course_id, user_id):
|
|
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({})
|
|
|
|
|
|
@require_GET
|
|
def search_similar_threads(request, course_id, commentable_id):
|
|
text = request.GET.get('text', None)
|
|
if text:
|
|
query_params = {
|
|
'text': text,
|
|
'commentable_id': commentable_id,
|
|
}
|
|
threads = cc.search_similar_threads(course_id, recursive=False, query_params=query_params)
|
|
else:
|
|
theads = []
|
|
context = {'threads': map(utils.extend_content, threads)}
|
|
return JsonResponse({
|
|
'html': render_to_string('discussion/_similar_posts.html', context)
|
|
})
|
|
|
|
|
|
@require_GET
|
|
def tags_autocomplete(request, course_id):
|
|
value = request.GET.get('q', None)
|
|
results = []
|
|
if value:
|
|
results = cc.tags_autocomplete(value)
|
|
return JsonResponse(results)
|
|
|
|
|
|
@require_POST
|
|
@login_required
|
|
@csrf.csrf_exempt
|
|
def upload(request, course_id): # ajax upload file to a question or answer
|
|
"""view that handles file upload via Ajax
|
|
"""
|
|
|
|
# check upload permission
|
|
result = ''
|
|
error = ''
|
|
new_file_name = ''
|
|
try:
|
|
# TODO authorization
|
|
#may raise exceptions.PermissionDenied
|
|
#if request.user.is_anonymous():
|
|
# msg = _('Sorry, anonymous users cannot upload files')
|
|
# raise exceptions.PermissionDenied(msg)
|
|
|
|
#request.user.assert_can_upload_file()
|
|
|
|
# check file type
|
|
f = request.FILES['file-upload']
|
|
file_extension = os.path.splitext(f.name)[1].lower()
|
|
if not file_extension in cc_settings.ALLOWED_UPLOAD_FILE_TYPES:
|
|
file_types = "', '".join(cc_settings.ALLOWED_UPLOAD_FILE_TYPES)
|
|
msg = _("allowed file types are '%(file_types)s'") % \
|
|
{'file_types': file_types}
|
|
raise exceptions.PermissionDenied(msg)
|
|
|
|
# generate new file name
|
|
new_file_name = str(
|
|
time.time()
|
|
).replace(
|
|
'.',
|
|
str(random.randint(0, 100000))
|
|
) + file_extension
|
|
|
|
file_storage = get_storage_class()()
|
|
# use default storage to store file
|
|
file_storage.save(new_file_name, f)
|
|
# check file size
|
|
# byte
|
|
size = file_storage.size(new_file_name)
|
|
if size > cc_settings.MAX_UPLOAD_FILE_SIZE:
|
|
file_storage.delete(new_file_name)
|
|
msg = _("maximum upload file size is %(file_size)sK") % \
|
|
{'file_size': cc_settings.MAX_UPLOAD_FILE_SIZE}
|
|
raise exceptions.PermissionDenied(msg)
|
|
|
|
except exceptions.PermissionDenied, e:
|
|
error = unicode(e)
|
|
except Exception, e:
|
|
print e
|
|
logging.critical(unicode(e))
|
|
error = _('Error uploading file. Please contact the site administrator. Thank you.')
|
|
|
|
if error == '':
|
|
result = 'Good'
|
|
file_url = file_storage.url(new_file_name)
|
|
parsed_url = urlparse.urlparse(file_url)
|
|
file_url = urlparse.urlunparse(
|
|
urlparse.ParseResult(
|
|
parsed_url.scheme,
|
|
parsed_url.netloc,
|
|
parsed_url.path,
|
|
'', '', ''
|
|
)
|
|
)
|
|
else:
|
|
result = ''
|
|
file_url = ''
|
|
|
|
return JsonResponse({
|
|
'result': {
|
|
'msg': result,
|
|
'error': error,
|
|
'file_url': file_url,
|
|
}
|
|
})
|