From fdab215eb4d37b6aa977a2ad4ef873abe45e0714 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Mon, 23 Jul 2012 16:20:45 -0400 Subject: [PATCH] moved django_comment_client into edx --- lms/djangoapps/django_comment_client | 1 - .../django_comment_client/__init__.py | 0 .../django_comment_client/base/__init__.py | 0 .../django_comment_client/base/urls.py | 17 ++ .../django_comment_client/base/views.py | 141 +++++++++++++++ .../django_comment_client/forum/__init__.py | 0 .../django_comment_client/forum/urls.py | 8 + .../django_comment_client/forum/views.py | 168 ++++++++++++++++++ lms/djangoapps/django_comment_client/urls.py | 6 + 9 files changed, 340 insertions(+), 1 deletion(-) delete mode 120000 lms/djangoapps/django_comment_client create mode 100644 lms/djangoapps/django_comment_client/__init__.py create mode 100644 lms/djangoapps/django_comment_client/base/__init__.py create mode 100644 lms/djangoapps/django_comment_client/base/urls.py create mode 100644 lms/djangoapps/django_comment_client/base/views.py create mode 100644 lms/djangoapps/django_comment_client/forum/__init__.py create mode 100644 lms/djangoapps/django_comment_client/forum/urls.py create mode 100644 lms/djangoapps/django_comment_client/forum/views.py create mode 100644 lms/djangoapps/django_comment_client/urls.py diff --git a/lms/djangoapps/django_comment_client b/lms/djangoapps/django_comment_client deleted file mode 120000 index 7ff5047e93..0000000000 --- a/lms/djangoapps/django_comment_client +++ /dev/null @@ -1 +0,0 @@ -/Users/dementrock/coding/cs_comments_client_python/djangoapp \ No newline at end of file diff --git a/lms/djangoapps/django_comment_client/__init__.py b/lms/djangoapps/django_comment_client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/base/__init__.py b/lms/djangoapps/django_comment_client/base/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/base/urls.py b/lms/djangoapps/django_comment_client/base/urls.py new file mode 100644 index 0000000000..ec3ba7c625 --- /dev/null +++ b/lms/djangoapps/django_comment_client/base/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls.defaults import url, patterns +import django_comment_client.base.views + +urlpatterns = patterns('django_comment_client.base.views', + url(r'(?P[\w\-]+)/threads/create$', 'create_thread', name='create_thread'), + url(r'threads/(?P[\w\-]+)/update$', 'update_thread', name='update_thread'), + url(r'threads/(?P[\w\-]+)/reply$', 'create_comment', name='create_comment'), + url(r'threads/(?P[\w\-]+)/delete', 'delete_thread', name='delete_thread'), + url(r'comments/(?P[\w\-]+)/update$', 'update_comment', name='update_comment'), + url(r'comments/(?P[\w\-]+)/endorse$', 'endorse_comment', name='endorse_comment'), + url(r'comments/(?P[\w\-]+)/reply$', 'create_sub_comment', name='create_sub_comment'), + url(r'comments/(?P[\w\-]+)/delete$', 'delete_comment', name='delete_comment'), + url(r'comments/(?P[\w\-]+)/upvote$', 'vote_for_comment', {'value': 'up'}, name='upvote_comment'), + url(r'comments/(?P[\w\-]+)/downvote$', 'vote_for_comment', {'value': 'down'}, name='downvote_comment'), + url(r'threads/(?P[\w\-]+)/upvote$', 'vote_for_thread', {'value': 'up'}, name='upvote_thread'), + url(r'threads/(?P[\w\-]+)/downvote$', 'vote_for_thread', {'value': 'down'}, name='downvote_thread'), +) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py new file mode 100644 index 0000000000..291bb4c732 --- /dev/null +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -0,0 +1,141 @@ +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_POST, require_GET +from django.http import HttpResponse +from django.utils import simplejson + +import comment_client + +class JsonResponse(HttpResponse): + def __init__(self, data=None): + content = simplejson.dumps(data, + indent=2, + ensure_ascii=False) + super(JsonResponse, self).__init__(content, + mimetype='application/json; charset=utf8') + +class JsonError(HttpResponse): + def __init__(self, status, error_message=""): + content = simplejson.dumps({'errors': error_message}, + indent=2, + ensure_ascii=False) + super(JsonError, self).__init__(content, + status=status, + mimetype='application/json; charset=utf8') + +def thread_author_only(fn): + def verified_fn(request, *args, **kwargs): + thread_id = args.get('thread_id', False) or \ + kwargs.get('thread_id', False) + thread = comment_client.get_thread(thread_id) + if request.user.id == thread['user_id']: + return fn(request, *args, **kwargs) + else: + return JsonError(400, "unauthorized") + return verified_fn + +def comment_author_only(fn): + def verified_fn(request, *args, **kwargs): + comment_id = args.get('comment_id', False) or \ + kwargs.get('comment_id', False) + comment = comment_client.get_comment(comment_id) + if request.user.id == comment['user_id']: + return fn(request, *args, **kwargs) + else: + return JsonError(400, "unauthorized") + return verified_fn + +def instructor_only(fn): #TODO add instructor verification + return fn + +def extract(dic, keys): + return {k: dic[k] for k in keys} + +@login_required +@require_POST +def create_thread(request, commentable_id): + attributes = extract(request.POST, ['body', 'title']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_thread(commentable_id, attributes) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def update_thread(request, thread_id): + attributes = extract(request.POST, ['body', 'title']) + response = comment_client.update_thread(thread_id, attributes) + return JsonResponse(response) + +@login_required +@require_POST +def create_comment(request, thread_id): + attributes = extract(request.POST, ['body']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_comment(thread_id, attributes) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def delete_thread(request, thread_id): + response = comment_client.delete_thread(thread_id) + return JsonResponse(response) + +@thread_author_only +@login_required +@require_POST +def update_comment(request, comment_id): + attributes = extract(request.POST, ['body']) + response = comment_client.update_comment(comment_id, attributes) + return JsonResponse(response) + +@instructor_only +@login_required +@require_POST +def endorse_comment(request, comment_id): + attributes = extract(request.POST, ['endorsed']) + response = comment_client.update_comment(comment_id, attributes) + return JsonResponse(response) + +@login_required +@require_POST +def create_sub_comment(request, comment_id): + attributes = extract(request.POST, ['body']) + attributes['user_id'] = request.user.id + attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow + response = comment_client.create_sub_comment(comment_id, attributes) + return JsonResponse(response) + +@comment_author_only +@login_required +@require_POST +def delete_comment(request, comment_id): + response = comment_client.delete_comment(comment_id) + return JsonResponse(response) + +@login_required +@require_POST +def vote_for_comment(request, comment_id, value): + user_id = request.user.id + response = comment_client.vote_for_comment(comment_id, user_id, value) + return JsonResponse(response) + +@login_required +@require_POST +def vote_for_thread(request, thread_id, value): + user_id = request.user.id + response = comment_client.vote_for_thread(thread_id, user_id, value) + return JsonResponse(response) + +#undo vote: disabled for now + + +@login_required +@require_GET +def search(request): + text = request.GET.get('text', None) + commentable_id = request.GET.get('commentable_id', None) + response = comment_client.search(text, commentable_id) + return JsonResponse(response) diff --git a/lms/djangoapps/django_comment_client/forum/__init__.py b/lms/djangoapps/django_comment_client/forum/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/forum/urls.py b/lms/djangoapps/django_comment_client/forum/urls.py new file mode 100644 index 0000000000..3a724ebee4 --- /dev/null +++ b/lms/djangoapps/django_comment_client/forum/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import url, patterns +import django_comment_client.forum.views + +urlpatterns = patterns('django_comment_client.forum.views', + url(r'search$', 'search', name='search'), + url(r'threads/(?P\w+)$', 'single_thread', name='single_thread'), + url(r'(?P[\w\.\-]+)/(?P\w+)$', 'forum_form_discussion', name='forum_form_discussion'), +) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py new file mode 100644 index 0000000000..f6f893705e --- /dev/null +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -0,0 +1,168 @@ +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_POST +from django.http import HttpResponse +from django.utils import simplejson +from django.core.context_processors import csrf + +from mitxmako.shortcuts import render_to_response, render_to_string +from courseware.courses import check_course +from courseware.models import StudentModuleCache +from courseware.module_render import get_module, get_section +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore + +from importlib import import_module + +from django.conf import settings + +import comment_client +import dateutil +from dateutil.tz import tzlocal +from datehelper import time_ago_in_words + +import operator +import itertools + +_FULLMODULES = None +_DISCUSSIONINFO = None + +def get_full_modules(): + global _FULLMODULES + if not _FULLMODULES: + class_path = settings.MODULESTORE['default']['ENGINE'] + module_path, _, class_name = class_path.rpartition('.') + class_ = getattr(import_module(module_path), class_name) + modulestore = class_(eager=True, **settings.MODULESTORE['default']['OPTIONS']) + _FULLMODULES = modulestore.modules + return _FULLMODULES + +def get_categorized_discussion_info(request, user, course, course_name, url_course_id): + """ + return a dict of the form {category: modules} + """ + global _DISCUSSIONINFO + if not _DISCUSSIONINFO: + + _is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ + and x[0].dict()['course'] == course_name + + _get_module_descriptor = operator.itemgetter(1) + + def _get_module(module_descriptor): + print module_descriptor + module = get_module(user, request, module_descriptor.location, student_module_cache)[0] + return module + + def _extract_info(module): + return { + 'title': module.title, + 'discussion_id': module.discussion_id, + 'category': module.category, + } + + discussion_module_descriptors = map(_get_module_descriptor, + filter(_is_course_discussion, + get_full_modules().items())) + + student_module_cache = StudentModuleCache(user, course) + + discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) + + _DISCUSSIONINFO = dict((category, list(l)) \ + for category, l in itertools.groupby(discussion_info, operator.itemgetter('category'))) + + _DISCUSSIONINFO['General'] = [{ + 'title': 'General discussion', + 'discussion_id': url_course_id, + 'category': 'General', + }] + + return _DISCUSSIONINFO + +def render_accordion(request, course, discussion_info, discussion_id): + context = dict([ + ('course', course), + ('discussion_info', discussion_info), + ('active', discussion_id), # TODO change this later + ('csrf', csrf(request)['csrf_token'])]) + + return render_to_string('discussion/accordion.html', context) + +def render_discussion(request, threads, discussion_id=None, search_text=''): + context = { + 'threads': threads, + 'time_ago_in_words': time_ago_in_words, + 'parse': dateutil.parser.parse, + 'discussion_id': discussion_id, + 'search_bar': render_search_bar(request, discussion_id, text=search_text), + } + return render_to_string('discussion/inline.html', context) + +def render_search_bar(request, discussion_id=None, text=''): + if not discussion_id: + return '' + context = { + 'discussion_id': discussion_id, + 'text': text, + } + return render_to_string('discussion/search_bar.html', context) + +def forum_form_discussion(request, course_id, discussion_id): + + course_id = course_id.replace('-', '/') + course = check_course(course_id) + + _, course_name, _ = course_id.split('/') + + url_course_id = course_id.replace('/', '_').replace('.', '_') + + discussion_info = get_categorized_discussion_info(request, request.user, course, course_name, url_course_id) + + search_text = request.GET.get('text', '') + + if len(search_text) > 0: + threads = comment_client.search(search_text, discussion_id) + else: + threads = comment_client.get_threads(discussion_id, recursive=False) + + context = { + 'csrf': csrf(request)['csrf_token'], + 'COURSE_TITLE': course.title, + 'course': course, + 'init': '', + 'content': render_discussion(request, threads, discussion_id, search_text), + 'accordion': render_accordion(request, course, discussion_info, discussion_id), + } + + return render_to_response('discussion/index.html', context) + +def render_single_thread(request, thread_id): + context = { + 'thread': comment_client.get_thread(thread_id, recursive=True), + 'time_ago_in_words': time_ago_in_words, + 'parse': dateutil.parser.parse, + } + return render_to_string('discussion/single_thread.html', context) + +def single_thread(request, thread_id): + + context = { + 'csrf': csrf(request)['csrf_token'], + 'init': '', + 'content': render_single_thread(request, thread_id), + 'accordion': '', + } + + return render_to_response('discussion/index.html', context) + +def search(request): + text = request.GET.get('text', None) + threads = comment_client.search(text) + context = { + 'csrf': csrf(request)['csrf_token'], + 'init': '', + 'content': render_discussion(request, threads, search_text=text), + 'accordion': '', + } + + return render_to_response('discussion/index.html', context) diff --git a/lms/djangoapps/django_comment_client/urls.py b/lms/djangoapps/django_comment_client/urls.py new file mode 100644 index 0000000000..959b5fa2ca --- /dev/null +++ b/lms/djangoapps/django_comment_client/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import url, patterns, include + +urlpatterns = patterns('', + url(r'forum/', include('django_comment_client.forum.urls')), + url(r'', include('django_comment_client.base.urls')), +)