Commit 54ea3dba by Rocky Duan

show news & options when submit comment

parent bedeac3a
import logging import logging
import urllib import urllib
from functools import partial
from django.conf import settings from django.conf import settings
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -16,6 +18,7 @@ from module_render import toc_for_course, get_module, get_section ...@@ -16,6 +18,7 @@ from module_render import toc_for_course, get_module, get_section
from models import StudentModuleCache from models import StudentModuleCache
from student.models import UserProfile from student.models import UserProfile
from multicourse import multicourse_settings from multicourse import multicourse_settings
from django_comment_client.utils import get_discussion_title
from util.cache import cache, cache_if_anonymous from util.cache import cache, cache_if_anonymous
from student.models import UserTestGroup from student.models import UserTestGroup
...@@ -23,6 +26,11 @@ from courseware import grades ...@@ -23,6 +26,11 @@ from courseware import grades
from courseware.courses import check_course from courseware.courses import check_course
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
import comment_client
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
template_imports = {'urllib': urllib} template_imports = {'urllib': urllib}
...@@ -249,3 +257,24 @@ def course_info(request, course_id): ...@@ -249,3 +257,24 @@ def course_info(request, course_id):
course = check_course(course_id) course = check_course(course_id)
return render_to_response('info.html', {'course': course}) return render_to_response('info.html', {'course': course})
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): ...@@ -71,8 +71,11 @@ def update_thread(request, course_id, thread_id):
@require_POST @require_POST
def create_comment(request, course_id, thread_id): def create_comment(request, course_id, thread_id):
attributes = extract(request.POST, ['body']) 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['course_id'] = course_id
attributes['auto_subscribe'] = bool(request.POST.get('autowatch', False))
print attributes
response = comment_client.create_comment(thread_id, attributes) response = comment_client.create_comment(thread_id, attributes)
return JsonResponse(response) return JsonResponse(response)
...@@ -103,8 +106,10 @@ def endorse_comment(request, course_id, comment_id): ...@@ -103,8 +106,10 @@ def endorse_comment(request, course_id, comment_id):
@require_POST @require_POST
def create_sub_comment(request, course_id, comment_id): def create_sub_comment(request, course_id, comment_id):
attributes = extract(request.POST, ['body']) attributes = extract(request.POST, ['body'])
attributes['user_id'] = request.user.id if request.POST.get('anonymous', 'false').lower() == 'false':
attributes['course_id'] = "1" # TODO either remove this or pass this parameter somehow 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) response = comment_client.create_sub_comment(comment_id, attributes)
return JsonResponse(response) return JsonResponse(response)
......
...@@ -6,79 +6,15 @@ from django.core.context_processors import csrf ...@@ -6,79 +6,15 @@ from django.core.context_processors import csrf
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from courseware.courses import check_course 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 comment_client
import dateutil import dateutil
from dateutil.tz import tzlocal from dateutil.tz import tzlocal
from datehelper import time_ago_in_words from datehelper import time_ago_in_words
import operator from django_comment_client.utils import get_categorized_discussion_info
import itertools
import json
_FULLMODULES = None import json
_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): def render_accordion(request, course, discussion_info, discussion_id):
context = { context = {
...@@ -118,7 +54,7 @@ def forum_form_discussion(request, course_id, discussion_id): ...@@ -118,7 +54,7 @@ def forum_form_discussion(request, course_id, discussion_id):
url_course_id = course_id.replace('/', '_').replace('.', '_') 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', '') search_text = request.GET.get('text', '')
......
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 = ...@@ -122,6 +122,8 @@ Discussion =
$discussionContent = $content.children(".discussion-content") $discussionContent = $content.children(".discussion-content")
$local = generateLocal($discussionContent) $local = generateLocal($discussionContent)
id = $content.attr("_id")
discussionContentHoverIn = -> discussionContentHoverIn = ->
status = $discussionContent.attr("status") || "normal" status = $discussionContent.attr("status") || "normal"
if status == "normal" if status == "normal"
...@@ -138,15 +140,32 @@ Discussion = ...@@ -138,15 +140,32 @@ Discussion =
$discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut)
handleReply = (elem) -> handleReply = (elem) ->
editView = $local(".discussion-content-edit") editView = $local(".discussion-content-edit")
if editView.length if editView.length
editView.show() editView.show()
else else
editView = $("<div>").addClass("discussion-content-edit") 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) $discussionContent.append(editView)
cancelReply = generateDiscussionLink("discussion-cancel-reply", "Cancel", handleCancelReply) cancelReply = generateDiscussionLink("discussion-cancel-reply", "Cancel", handleCancelReply)
submitReply = generateDiscussionLink("discussion-submit-reply", "Submit", handleSubmitReply) submitReply = generateDiscussionLink("discussion-submit-reply", "Submit", handleSubmitReply)
...@@ -166,20 +185,24 @@ Discussion = ...@@ -166,20 +185,24 @@ Discussion =
handleSubmitReply = (elem) -> handleSubmitReply = (elem) ->
if $content.hasClass("thread") if $content.hasClass("thread")
url = Discussion.urlFor('create_comment', $content.attr("_id")) url = Discussion.urlFor('create_comment', id)
else if $content.hasClass("comment") else if $content.hasClass("comment")
url = Discussion.urlFor('create_sub_comment', $content.attr("_id")) url = Discussion.urlFor('create_sub_comment', id)
else else
return return
body = $local(".comment-edit").val() 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" if textStatus == "success"
Discussion.handleAnchorAndReload(response) Discussion.handleAnchorAndReload(response)
, 'json' , 'json'
handleVote = (elem, value) -> handleVote = (elem, value) ->
contentType = if $content.hasClass("thread") then "thread" else "comment" 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) -> $.post url, {}, (response, textStatus) ->
if textStatus == "success" if textStatus == "success"
Discussion.handleAnchorAndReload(response) Discussion.handleAnchorAndReload(response)
......
...@@ -155,12 +155,13 @@ $discussion_input_width: 60%; ...@@ -155,12 +155,13 @@ $discussion_input_width: 60%;
margin-top: 10px; margin-top: 10px;
overflow: hidden; overflow: hidden;
.discussion-content-edit { .discussion-content-edit {
margin-left: $comment_margin_left;
.comment-edit { .comment-edit {
@include discussion-font; @include discussion-font;
width: $discussion_input_width !important; width: $discussion_input_width !important;
margin-left: $comment_margin_left;
font-size: $comment_body_size; font-size: $comment_body_size;
margin-top: 10px; margin-top: 10px;
display: block;
} }
} }
} }
......
@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;
}
}
}
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
@import 'about_pages'; @import 'about_pages';
@import 'press_release'; @import 'press_release';
@import 'discussion'; @import 'discussion';
@import 'news';
// Courseware styles // Courseware styles
......
...@@ -16,6 +16,7 @@ def url_class(url): ...@@ -16,6 +16,7 @@ def url_class(url):
% if user.is_authenticated(): % if user.is_authenticated():
<li class="book"><a href="${reverse('book', args=[course.id])}" class="${url_class('book')}">Textbook</a></li> <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="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 % endif
<li class="wiki"><a href="${reverse('wiki_root', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li> <li class="wiki"><a href="${reverse('wiki_root', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li>
% if user.is_authenticated(): % if user.is_authenticated():
......
...@@ -12,17 +12,19 @@ ...@@ -12,17 +12,19 @@
%> %>
<div class="thread" _id="${thread['id']}"> <div class="thread" _id="${thread['id']}">
<div class="discussion-content"> <div class="discussion-content">
${render_vote(thread)} <div class="discussion-upper-wrapper clearfix">
<div class="discussion-right-wrapper clearfix"> ${render_vote(thread)}
<a class="thread-title" name="${thread['id']}" href="${url_for_thread}">${thread['title']}</a> <div class="discussion-right-wrapper clearfix">
<div class="discussion-content-view"> <a class="thread-title" name="${thread['id']}" href="${url_for_thread}">${thread['title']}</a>
<div class="thread-body">${thread['body']}</div> <div class="discussion-content-view">
<div class="info"> <div class="thread-body">${thread['body']}</div>
${render_info(thread)} <div class="info">
% if edit_thread: ${render_info(thread)}
${render_reply()} % if edit_thread:
${render_edit()} ${render_reply()}
% endif ${render_edit()}
% endif
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -59,7 +61,12 @@ ...@@ -59,7 +61,12 @@
</%def> </%def>
<%def name="render_info(content)"> <%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>
<%def name="render_reply()"> <%def name="render_reply()">
......
<%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>
<%! 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>
...@@ -142,7 +142,7 @@ $(function() { ...@@ -142,7 +142,7 @@ $(function() {
%for chapter in courseware_summary: %for chapter in courseware_summary:
%if not chapter['chapter'] == "hidden": %if not chapter['chapter'] == "hidden":
<li> <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> ${ chapter['chapter'] }</a></h2>
<ol class="sections"> <ol class="sections">
...@@ -154,7 +154,7 @@ $(function() { ...@@ -154,7 +154,7 @@ $(function() {
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else "" 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['section'] }</a> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</h3>
${section['format']} ${section['format']}
%if 'due' in section and section['due']!="": %if 'due' in section and section['due']!="":
......
...@@ -130,6 +130,8 @@ if settings.COURSEWARE_ENABLED: ...@@ -130,6 +130,8 @@ if settings.COURSEWARE_ENABLED:
'courseware.views.profile', name="profile"), 'courseware.views.profile', name="profile"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$',
'courseware.views.profile'), 'courseware.views.profile'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/news$',
'courseware.views.news', name="news"),
# discussion # discussion
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment