show news & options when submit comment
This commit is contained in:
@@ -4,6 +4,8 @@ import logging
|
||||
import urllib
|
||||
import itertools
|
||||
|
||||
from functools import partial
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -20,6 +22,7 @@ from module_render import toc_for_course, get_module, get_section
|
||||
from models import StudentModuleCache
|
||||
from student.models import UserProfile
|
||||
from multicourse import multicourse_settings
|
||||
from django_comment_client.utils import get_discussion_title
|
||||
|
||||
from util.cache import cache, cache_if_anonymous
|
||||
from student.models import UserTestGroup, CourseEnrollment
|
||||
@@ -27,6 +30,11 @@ from courseware import grades
|
||||
from courseware.courses import check_course
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
import comment_client
|
||||
|
||||
|
||||
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
template_imports = {'urllib': urllib}
|
||||
@@ -259,7 +267,6 @@ def course_info(request, course_id):
|
||||
|
||||
return render_to_response('info.html', {'course': course})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def course_about(request, course_id):
|
||||
@@ -287,3 +294,24 @@ def university_profile(request, org_id):
|
||||
template_file = "university_profile/{0}.html".format(org_id).lower()
|
||||
|
||||
return render_to_response(template_file, context)
|
||||
|
||||
def render_notifications(request, course, notifications):
|
||||
context = {
|
||||
'notifications': notifications,
|
||||
'get_discussion_title': partial(get_discussion_title, request=request, course=course),
|
||||
'course': course,
|
||||
}
|
||||
return render_to_string('notifications.html', context)
|
||||
|
||||
@login_required
|
||||
def news(request, course_id):
|
||||
course = check_course(course_id)
|
||||
|
||||
notifications = comment_client.get_notifications(request.user.id)
|
||||
|
||||
context = {
|
||||
'course': course,
|
||||
'content': render_notifications(request, course, notifications),
|
||||
}
|
||||
|
||||
return render_to_response('news.html', context)
|
||||
|
||||
@@ -71,8 +71,11 @@ def update_thread(request, course_id, thread_id):
|
||||
@require_POST
|
||||
def create_comment(request, course_id, thread_id):
|
||||
attributes = extract(request.POST, ['body'])
|
||||
attributes['user_id'] = request.user.id
|
||||
if request.POST.get('anonymous', 'false').lower() == 'false':
|
||||
attributes['user_id'] = request.user.id
|
||||
attributes['course_id'] = course_id
|
||||
attributes['auto_subscribe'] = bool(request.POST.get('autowatch', False))
|
||||
print attributes
|
||||
response = comment_client.create_comment(thread_id, attributes)
|
||||
return JsonResponse(response)
|
||||
|
||||
@@ -103,8 +106,10 @@ def endorse_comment(request, course_id, comment_id):
|
||||
@require_POST
|
||||
def create_sub_comment(request, course_id, 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
|
||||
if request.POST.get('anonymous', 'false').lower() == 'false':
|
||||
attributes['user_id'] = request.user.id
|
||||
attributes['course_id'] = course_id
|
||||
attributes['auto_subscribe'] = bool(request.POST.get('autowatch', False))
|
||||
response = comment_client.create_sub_comment(comment_id, attributes)
|
||||
return JsonResponse(response)
|
||||
|
||||
|
||||
@@ -6,80 +6,16 @@ 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
|
||||
from django_comment_client.utils import get_categorized_discussion_info
|
||||
|
||||
import json
|
||||
|
||||
_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.discussion_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 = {
|
||||
'course': course,
|
||||
@@ -118,7 +54,7 @@ def forum_form_discussion(request, course_id, discussion_id):
|
||||
|
||||
url_course_id = course_id.replace('/', '_').replace('.', '_')
|
||||
|
||||
discussion_info = get_categorized_discussion_info(request, request.user, course, course_name, url_course_id)
|
||||
discussion_info = get_categorized_discussion_info(request, course)#request.user, course, course_name, url_course_id)
|
||||
|
||||
search_text = request.GET.get('text', '')
|
||||
|
||||
|
||||
93
lms/djangoapps/django_comment_client/utils.py
Normal file
93
lms/djangoapps/django_comment_client/utils.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from importlib import import_module
|
||||
from courseware.models import StudentModuleCache
|
||||
from courseware.module_render import get_module
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from django.conf import settings
|
||||
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, course):
|
||||
"""
|
||||
return a dict of the form {category: modules}
|
||||
"""
|
||||
global _DISCUSSIONINFO
|
||||
if not _DISCUSSIONINFO:
|
||||
initialize_discussion_info(request, course)
|
||||
return _DISCUSSIONINFO['categorized']
|
||||
|
||||
def get_discussion_title(request, course, discussion_id):
|
||||
global _DISCUSSIONINFO
|
||||
if not _DISCUSSIONINFO:
|
||||
initialize_discussion_info(request, course)
|
||||
title = _DISCUSSIONINFO['by_id'].get(discussion_id, {}).get('title', '(no title)')
|
||||
if title == '(no title)':
|
||||
print "title shouldn't be none"
|
||||
import pdb; pdb.set_trace()
|
||||
return title
|
||||
|
||||
def initialize_discussion_info(request, course):
|
||||
|
||||
global _DISCUSSIONINFO
|
||||
if _DISCUSSIONINFO:
|
||||
return
|
||||
|
||||
course_id = course.id
|
||||
_, course_name, _ = course_id.split('/')
|
||||
user = request.user
|
||||
url_course_id = course_id.replace('/', '_').replace('.', '_')
|
||||
|
||||
_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.discussion_category,
|
||||
}
|
||||
|
||||
def _pack_with_id(info):
|
||||
return (info['discussion_id'], info)
|
||||
|
||||
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 = {}
|
||||
|
||||
_DISCUSSIONINFO['by_id'] = dict(map(_pack_with_id, discussion_info))
|
||||
|
||||
_DISCUSSIONINFO['categorized'] = dict((category, list(l)) \
|
||||
for category, l in itertools.groupby(discussion_info, operator.itemgetter('category')))
|
||||
|
||||
_DISCUSSIONINFO['categorized']['General'] = [{
|
||||
'title': 'General discussion',
|
||||
'discussion_id': url_course_id,
|
||||
'category': 'General',
|
||||
}]
|
||||
@@ -122,6 +122,8 @@ Discussion =
|
||||
$discussionContent = $content.children(".discussion-content")
|
||||
$local = generateLocal($discussionContent)
|
||||
|
||||
id = $content.attr("_id")
|
||||
|
||||
discussionContentHoverIn = ->
|
||||
status = $discussionContent.attr("status") || "normal"
|
||||
if status == "normal"
|
||||
@@ -138,15 +140,32 @@ Discussion =
|
||||
|
||||
$discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut)
|
||||
|
||||
|
||||
|
||||
handleReply = (elem) ->
|
||||
editView = $local(".discussion-content-edit")
|
||||
if editView.length
|
||||
editView.show()
|
||||
else
|
||||
editView = $("<div>").addClass("discussion-content-edit")
|
||||
editView.append($("<textarea>").addClass("comment-edit"))
|
||||
|
||||
textarea = $("<textarea>").addClass("comment-edit")
|
||||
editView.append(textarea)
|
||||
|
||||
anonymousCheckbox = $("<input>").attr("type", "checkbox")
|
||||
.addClass("discussion-post-anonymously")
|
||||
.attr("id", "discussion-post-anonymously-#{id}")
|
||||
anonymousLabel = $("<label>").attr("for", "discussion-post-anonymously-#{id}")
|
||||
.html("post anonymously")
|
||||
editView.append(anonymousCheckbox).append(anonymousLabel)
|
||||
|
||||
if $discussionContent.parent(".thread").attr("_id") not in $$user_info.subscribed_thread_ids
|
||||
watchCheckbox = $("<input>").attr("type", "checkbox")
|
||||
.addClass("discussion-auto-watch")
|
||||
.attr("id", "discussion-auto-watch-#{id}")
|
||||
.attr("checked", "")
|
||||
watchLabel = $("<label>").attr("for", "discussion-auto-watch-#{id}")
|
||||
.html("watch this thread")
|
||||
editView.append(watchCheckbox).append(watchLabel)
|
||||
|
||||
$discussionContent.append(editView)
|
||||
cancelReply = generateDiscussionLink("discussion-cancel-reply", "Cancel", handleCancelReply)
|
||||
submitReply = generateDiscussionLink("discussion-submit-reply", "Submit", handleSubmitReply)
|
||||
@@ -166,20 +185,24 @@ Discussion =
|
||||
|
||||
handleSubmitReply = (elem) ->
|
||||
if $content.hasClass("thread")
|
||||
url = Discussion.urlFor('create_comment', $content.attr("_id"))
|
||||
url = Discussion.urlFor('create_comment', id)
|
||||
else if $content.hasClass("comment")
|
||||
url = Discussion.urlFor('create_sub_comment', $content.attr("_id"))
|
||||
url = Discussion.urlFor('create_sub_comment', id)
|
||||
else
|
||||
return
|
||||
body = $local(".comment-edit").val()
|
||||
$.post url, {body: body}, (response, textStatus) ->
|
||||
|
||||
anonymous = false || $local(".discussion-post-anonymously").is(":checked")
|
||||
autowatch = false || $local(".discussion-auto-watch").is(":checked")
|
||||
|
||||
$.post url, {body: body, anonymous: anonymous, autowatch: autowatch}, (response, textStatus) ->
|
||||
if textStatus == "success"
|
||||
Discussion.handleAnchorAndReload(response)
|
||||
, 'json'
|
||||
|
||||
handleVote = (elem, value) ->
|
||||
contentType = if $content.hasClass("thread") then "thread" else "comment"
|
||||
url = Discussion.urlFor("#{value}vote_#{contentType}", $content.attr("_id"))
|
||||
url = Discussion.urlFor("#{value}vote_#{contentType}", id)
|
||||
$.post url, {}, (response, textStatus) ->
|
||||
if textStatus == "success"
|
||||
Discussion.handleAnchorAndReload(response)
|
||||
|
||||
@@ -155,12 +155,13 @@ $discussion_input_width: 60%;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
.discussion-content-edit {
|
||||
margin-left: $comment_margin_left;
|
||||
.comment-edit {
|
||||
@include discussion-font;
|
||||
width: $discussion_input_width !important;
|
||||
margin-left: $comment_margin_left;
|
||||
font-size: $comment_body_size;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
lms/static/sass/_news.scss
Normal file
19
lms/static/sass/_news.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
@mixin news-font {
|
||||
font-family: "Comic Sans MS", cursive, sans-serif !important;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
@include news-font;
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
.notification {
|
||||
@include news-font;
|
||||
margin-top: 15px;
|
||||
margin-botton: 15px;
|
||||
|
||||
a {
|
||||
@include news-font;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,3 +28,4 @@
|
||||
@import 'multicourse/error-pages';
|
||||
@import 'multicourse/help';
|
||||
@import 'discussion';
|
||||
@import 'news';
|
||||
|
||||
@@ -16,6 +16,7 @@ def url_class(url):
|
||||
% if user.is_authenticated():
|
||||
<li class="book"><a href="${reverse('book', args=[course.id])}" class="${url_class('book')}">Textbook</a></li>
|
||||
<li class="discussion"><a href="${reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id, course.id.replace('/', '_').replace('.', '_')])}" class="${url_class('discussion')}">Discussion</a></li>
|
||||
<li class="news"><a href="${reverse('news', args=[course.id])}" class="${url_class('news')}">News</a></li>
|
||||
% endif
|
||||
<li class="wiki"><a href="${reverse('wiki_root', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li>
|
||||
% if user.is_authenticated():
|
||||
|
||||
@@ -12,17 +12,19 @@
|
||||
%>
|
||||
<div class="thread" _id="${thread['id']}">
|
||||
<div class="discussion-content">
|
||||
${render_vote(thread)}
|
||||
<div class="discussion-right-wrapper clearfix">
|
||||
<a class="thread-title" name="${thread['id']}" href="${url_for_thread}">${thread['title']}</a>
|
||||
<div class="discussion-content-view">
|
||||
<div class="thread-body">${thread['body']}</div>
|
||||
<div class="info">
|
||||
${render_info(thread)}
|
||||
% if edit_thread:
|
||||
${render_reply()}
|
||||
${render_edit()}
|
||||
% endif
|
||||
<div class="discussion-upper-wrapper clearfix">
|
||||
${render_vote(thread)}
|
||||
<div class="discussion-right-wrapper clearfix">
|
||||
<a class="thread-title" name="${thread['id']}" href="${url_for_thread}">${thread['title']}</a>
|
||||
<div class="discussion-content-view">
|
||||
<div class="thread-body">${thread['body']}</div>
|
||||
<div class="info">
|
||||
${render_info(thread)}
|
||||
% if edit_thread:
|
||||
${render_reply()}
|
||||
${render_edit()}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,7 +61,12 @@
|
||||
</%def>
|
||||
|
||||
<%def name="render_info(content)">
|
||||
${time_ago_in_words(parse(content['updated_at']))} ago by user No.${content['user_id']}
|
||||
${time_ago_in_words(parse(content['updated_at']))} ago by
|
||||
% if content.get('user_id', False):
|
||||
user No.${content['user_id']}
|
||||
% else:
|
||||
anonymous
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_reply()">
|
||||
|
||||
21
lms/templates/news.html
Normal file
21
lms/templates/news.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<%inherit file="main.html" />
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%block name="bodyclass">courseware news</%block>
|
||||
<%block name="title"><title>News – MITx 6.002x</title></%block>
|
||||
|
||||
<%block name="headextra">
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
</%block>
|
||||
|
||||
<%include file="course_navigation.html" args="active_page='news'" />
|
||||
|
||||
<section class="container">
|
||||
<div class="course-wrapper">
|
||||
<section class="course-content">
|
||||
${content}
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
48
lms/templates/notifications.html
Normal file
48
lms/templates/notifications.html
Normal file
@@ -0,0 +1,48 @@
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
|
||||
<%
|
||||
def url_for_thread(thread_id):
|
||||
return reverse('django_comment_client.forum.views.single_thread', args=[course.id, thread_id])
|
||||
%>
|
||||
|
||||
<%
|
||||
def url_for_comment(thread_id, comment_id):
|
||||
return reverse('django_comment_client.forum.views.single_thread', args=[course.id, thread_id]) + "#" + comment_id
|
||||
%>
|
||||
|
||||
<%
|
||||
def url_for_discussion(discussion_id):
|
||||
return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id, discussion_id])
|
||||
%>
|
||||
|
||||
<%
|
||||
def discussion_title(discussion_id):
|
||||
return get_discussion_title(discussion_id=discussion_id)
|
||||
%>
|
||||
|
||||
|
||||
<div class="notifications">
|
||||
% for notification in notifications:
|
||||
${render_notification(notification)}
|
||||
% endfor
|
||||
</div>
|
||||
|
||||
<%def name="render_notification(notification)">
|
||||
<div class="notification">
|
||||
<% info = notification['info'] %>
|
||||
% if notification['notification_type'] == 'post_reply':
|
||||
User No.${notification['actor_id']} posted a
|
||||
<a href="${url_for_comment(info['thread_id'], info['comment_id'])}">comment</a>
|
||||
to the thread
|
||||
<a href="${url_for_thread(info['thread_id'])}">${info['thread_title']}</a>
|
||||
in discussion
|
||||
<a href="${url_for_discussion(info['commentable_id'])}">${discussion_title(info['commentable_id'])}</a>
|
||||
% elif notification['notification_type'] == 'post_topic':
|
||||
User No.${notification['actor_id']} posted a new thread
|
||||
<a href="${url_for_thread(info['thread_id'])}">${info['thread_title']}</a>
|
||||
in discussion
|
||||
<a href="${url_for_discussion(info['commentable_id'])}">${discussion_title(info['commentable_id'])}</a>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
</%def>
|
||||
@@ -126,7 +126,7 @@ $(function() {
|
||||
%for chapter in courseware_summary:
|
||||
%if not chapter['chapter'] == "hidden":
|
||||
<li>
|
||||
<h2><a href="${reverse('courseware_chapter', args=format_url_params([chapter['course'], chapter['chapter']])) }">
|
||||
<h2><a href="javascript:void(0)">
|
||||
${ chapter['chapter'] }</a></h2>
|
||||
|
||||
<ol class="sections">
|
||||
@@ -138,7 +138,7 @@ $(function() {
|
||||
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
|
||||
%>
|
||||
|
||||
<h3><a href="${reverse('courseware_section', args=format_url_params([chapter['course'], chapter['chapter'], section['section']])) }">
|
||||
<h3><a href="javascript:void(0)">
|
||||
${ section['section'] }</a> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</h3>
|
||||
${section['format']}
|
||||
%if 'due' in section and section['due']!="":
|
||||
|
||||
@@ -135,6 +135,8 @@ if settings.COURSEWARE_ENABLED:
|
||||
'courseware.views.profile', name="profile"),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$',
|
||||
'courseware.views.profile'),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/news$',
|
||||
'courseware.views.news', name="news"),
|
||||
|
||||
# discussion
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/',
|
||||
|
||||
Reference in New Issue
Block a user