This reverts commit66889b8724, reversing changes made to0dd9c24e74. This was merged too early--it's still missing the filtering and display-your-cohort changes. Will need to revert this revert later.
481 lines
15 KiB
Python
481 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,
|
|
})
|
|
|
|
|
|
# Cohort the thread if the commentable is cohorted.
|
|
if is_commentable_cohorted(course_id, commentable_id):
|
|
user_group_id = get_cohort_id(request.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
|
|
|
|
thread.update_attributes(group_id=group_id)
|
|
|
|
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,
|
|
}
|
|
})
|