Commit b4828db1 by Ibrahim Awwal

User profile page redone to act like the inline discussion, except threads are

loaded into the page in a data attribute instead of loaded on request via ajax.
The user profile page also does not provide facilities for adding
comments/responses, which I think would clutter the page too much.

Also removed some unused stuff from views.py.
parent 1be378e6
...@@ -9,7 +9,7 @@ If you haven't done so already: ...@@ -9,7 +9,7 @@ If you haven't done so already:
brew install mongodb brew install mongodb
Make sure that you have mongodb running. You can simply open a new terminal tab and type: Make sure that you have mongodb running. You can simply open a new terminal tab and type:
mongod mongod
## Installing elasticsearch ## Installing elasticsearch
...@@ -72,9 +72,9 @@ For convenience, add the following environment variables to the terminal (assumi ...@@ -72,9 +72,9 @@ For convenience, add the following environment variables to the terminal (assumi
export DJANGO_SETTINGS_MODULE=lms.envs.dev export DJANGO_SETTINGS_MODULE=lms.envs.dev
export PYTHONPATH=. export PYTHONPATH=.
Now initialzie roles and permissions: Now initialzie roles and permissions, providing a course id eg.:
django-admin.py seed_permissions_roles django-admin.py seed_permissions_roles "MITx/6.002x/2012_Fall"
To assign yourself as a moderator, use the following command (assuming your username is "test", and the course id is "MITx/6.002x/2012_Fall"): To assign yourself as a moderator, use the following command (assuming your username is "test", and the course id is "MITx/6.002x/2012_Fall"):
......
...@@ -25,7 +25,7 @@ import xml.sax.saxutils as saxutils ...@@ -25,7 +25,7 @@ import xml.sax.saxutils as saxutils
THREADS_PER_PAGE = 200 THREADS_PER_PAGE = 200
INLINE_THREADS_PER_PAGE = 5 INLINE_THREADS_PER_PAGE = 5
PAGES_NEARBY_DELTA = 2 PAGES_NEARBY_DELTA = 2
escapedict = {'"': '"'}
log = logging.getLogger("edx.discussions") log = logging.getLogger("edx.discussions")
def _general_discussion_id(course_id): def _general_discussion_id(course_id):
...@@ -83,7 +83,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs): ...@@ -83,7 +83,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
thread['courseware_title'] = courseware_context['courseware_title'] thread['courseware_title'] = courseware_context['courseware_title']
context = { context = {
#'threads': map(utils.safe_content, threads), # TODO Delete, this is redundant with discussion_data 'threads': map(utils.safe_content, threads),
'discussion_id': discussion_id, 'discussion_id': discussion_id,
'user_id': user_id, 'user_id': user_id,
'course_id': course_id, 'course_id': course_id,
...@@ -94,7 +94,8 @@ def render_discussion(request, course_id, threads, *args, **kwargs): ...@@ -94,7 +94,8 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
'base_url': base_url, 'base_url': base_url,
'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))), 'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))),
'annotated_content_info': json.dumps(annotated_content_info), 'annotated_content_info': json.dumps(annotated_content_info),
'discussion_data': json.dumps({ (discussion_id or user_id): map(utils.safe_content, threads) }) #'discussion_data': json.dumps({ (discussion_id or user_id): map(utils.safe_content, threads) })
# TODO: Delete the above, nothing uses this
} }
context = dict(context.items() + query_params.items()) context = dict(context.items() + query_params.items())
return render_to_string(template, context) return render_to_string(template, context)
...@@ -161,17 +162,12 @@ def inline_discussion(request, course_id, discussion_id): ...@@ -161,17 +162,12 @@ def inline_discussion(request, course_id, discussion_id):
# checking for errors on request. Check and fix as needed. # checking for errors on request. Check and fix as needed.
raise Http404 raise Http404
# TODO: Remove all of this stuff or switch back to server side rendering once templates are mustache again
#html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \
# query_params=query_params)
def infogetter(thread): def infogetter(thread):
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info) return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {}) annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
return utils.JsonResponse({ return utils.JsonResponse({
# 'html': html,
'discussion_data': map(utils.safe_content, threads), 'discussion_data': map(utils.safe_content, threads),
'user_info': user_info, 'user_info': user_info,
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
...@@ -193,8 +189,6 @@ def forum_form_discussion(request, course_id): ...@@ -193,8 +189,6 @@ def forum_form_discussion(request, course_id):
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err: except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
raise Http404 raise Http404
#content = render_forum_discussion(request, course_id, threads, discussion_id=_general_discussion_id(course_id), query_params=query_params)
user_info = cc.User.from_django_user(request.user).to_dict() user_info = cc.User.from_django_user(request.user).to_dict()
def infogetter(thread): def infogetter(thread):
...@@ -208,8 +202,7 @@ def forum_form_discussion(request, course_id): ...@@ -208,8 +202,7 @@ def forum_form_discussion(request, course_id):
thread['courseware_title'] = courseware_context['courseware_title'] thread['courseware_title'] = courseware_context['courseware_title']
if request.is_ajax(): if request.is_ajax():
return utils.JsonResponse({ return utils.JsonResponse({
#'html': content, 'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads'
'discussion_data': threads,
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
}) })
else: else:
...@@ -223,11 +216,9 @@ def forum_form_discussion(request, course_id): ...@@ -223,11 +216,9 @@ def forum_form_discussion(request, course_id):
# course_id, # course_id,
#) #)
escapedict = {'"': '"'}
context = { context = {
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'course': course, 'course': course,
#'content': content,
#'recent_active_threads': recent_active_threads, #'recent_active_threads': recent_active_threads,
#'trending_tags': trending_tags, #'trending_tags': trending_tags,
'staff_access' : has_access(request.user, course, 'staff'), 'staff_access' : has_access(request.user, course, 'staff'),
...@@ -256,12 +247,12 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -256,12 +247,12 @@ def single_thread(request, course_id, discussion_id, thread_id):
annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info) annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info)
context = {'thread': thread.to_dict(), 'course_id': course_id} context = {'thread': thread.to_dict(), 'course_id': course_id}
# TODO: Remove completely or switch back to server side rendering # TODO: Remove completely or switch back to server side rendering
html = render_to_string('discussion/_ajax_single_thread.html', context) # html = render_to_string('discussion/_ajax_single_thread.html', context)
content = utils.safe_content(thread.to_dict()) content = utils.safe_content(thread.to_dict())
if courseware_context: if courseware_context:
content.update(courseware_context) content.update(courseware_context)
return utils.JsonResponse({ return utils.JsonResponse({
'html': html, #'html': html,
'content': content, 'content': content,
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
}) })
...@@ -293,7 +284,7 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -293,7 +284,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
#) #)
user_info = cc.User.from_django_user(request.user).to_dict() user_info = cc.User.from_django_user(request.user).to_dict()
escapedict = {'"': '"'}
def infogetter(thread): def infogetter(thread):
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info) return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
...@@ -303,13 +294,13 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -303,13 +294,13 @@ def single_thread(request, course_id, discussion_id, thread_id):
context = { context = {
'discussion_id': discussion_id, 'discussion_id': discussion_id,
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'init': '', 'init': '', #TODO: What is this?
'user_info': saxutils.escape(json.dumps(user_info),escapedict), 'user_info': saxutils.escape(json.dumps(user_info),escapedict),
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
'course': course, 'course': course,
#'recent_active_threads': recent_active_threads, #'recent_active_threads': recent_active_threads,
#'trending_tags': trending_tags, #'trending_tags': trending_tags,
'course_id': course.id, 'course_id': course.id, #TODO: Why pass both course and course.id to template?
'thread_id': thread_id, 'thread_id': thread_id,
'threads': saxutils.escape(json.dumps(threads), escapedict), 'threads': saxutils.escape(json.dumps(threads), escapedict),
'category_map': category_map, 'category_map': category_map,
...@@ -323,11 +314,15 @@ def user_profile(request, course_id, user_id): ...@@ -323,11 +314,15 @@ def user_profile(request, course_id, user_id):
course = get_course_with_access(request.user, course_id, 'load') course = get_course_with_access(request.user, course_id, 'load')
try: try:
profiled_user = cc.User(id=user_id, course_id=course_id) profiled_user = cc.User(id=user_id, course_id=course_id)
query_params = {
'page': request.GET.get('page', 1),
'per_page': INLINE_THREADS_PER_PAGE,
}
threads, page, num_pages = profiled_user.active_threads(query_params)
query_params['page'] = page query_params['page'] = page
query_params['num_pages'] = num_pages query_params['num_pages'] = num_pages
content = render_user_discussion(request, course_id, threads, user_id=user_id, query_params=query_params) # content = render_user_discussion(request, course_id, threads, user_id=user_id, query_params=query_params)
if request.is_ajax(): if request.is_ajax():
return utils.JsonResponse({ return utils.JsonResponse({
...@@ -335,12 +330,21 @@ def user_profile(request, course_id, user_id): ...@@ -335,12 +330,21 @@ def user_profile(request, course_id, user_id):
'discussion_data': map(utils.safe_content, threads), 'discussion_data': map(utils.safe_content, threads),
}) })
else: else:
user_info = cc.User.from_django_user(request.user).to_dict()
def infogetter(thread):
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
context = { context = {
'course': course, 'course': course,
'user': request.user, 'user': request.user,
'django_user': User.objects.get(id=user_id), 'django_user': User.objects.get(id=user_id),
'profiled_user': profiled_user.to_dict(), 'profiled_user': profiled_user.to_dict(),
'content': content, 'threads': saxutils.escape(json.dumps(threads), escapedict),
'user_info': saxutils.escape(json.dumps(user_info),escapedict),
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict),
# 'content': content,
} }
return render_to_response('discussion/user_profile.html', context) return render_to_response('discussion/user_profile.html', context)
......
...@@ -7,8 +7,10 @@ class Command(BaseCommand): ...@@ -7,8 +7,10 @@ class Command(BaseCommand):
help = 'Seed default permisssions and roles' help = 'Seed default permisssions and roles'
def handle(self, *args, **options): def handle(self, *args, **options):
if len(args) != 1: if len(args) == 0:
raise CommandError("The number of arguments does not match. ") raise CommandError("Please provide a course id")
if len(args) > 1:
raise CommandError("Too many arguments")
course_id = args[0] course_id = args[0]
administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0] administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0]
moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0] moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
......
...@@ -12,7 +12,16 @@ if Backbone? ...@@ -12,7 +12,16 @@ if Backbone?
discussion = new Discussion(threads) discussion = new Discussion(threads)
new DiscussionRouter({discussion: discussion}) new DiscussionRouter({discussion: discussion})
Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"}) Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"})
DiscussionProfileApp =
start: (elem) ->
element = $(elem)
window.$$course_id = element.data("course-id")
threads = element.data("threads")
user_info = element.data("user-info")
window.user = new DiscussionUser(user_info)
new DiscussionUserProfileView(el: element, collection: threads)
$ -> $ ->
$("section.discussion").each (index, elem) -> $("section.discussion").each (index, elem) ->
DiscussionApp.start(elem) DiscussionApp.start(elem)
$("section.discussion-user-threads").each (index, elem) ->
DiscussionProfileApp.start(elem)
if Backbone?
class @DiscussionThreadProfileView extends DiscussionContentView
expanded = false
events:
"click .discussion-vote": "toggleVote"
"click .action-follow": "toggleFollowing"
"click .expand-post": "expandPost"
"click .collapse-post": "collapsePost"
initLocal: ->
@$local = @$el.children(".discussion-article").children(".local")
@$delegateElement = @$local
initialize: ->
super()
@model.on "change", @updateModelDetails
render: ->
@template = DiscussionUtil.getTemplate("_profile_thread")
if not @model.has('abbreviatedBody')
@abbreviateBody()
params = $.extend(@model.toJSON(),{expanded: @expanded})
if not @model.get('anonymous')
params = $.extend(params, user:{username: @model.username, user_url: @model.user_url})
@$el.html(Mustache.render(@template, params))
@initLocal()
@delegateEvents()
@renderDogear()
@renderVoted()
@renderAttrs()
@$("span.timeago").timeago()
@convertMath()
if @expanded
@renderResponses()
@
renderDogear: ->
if window.user.following(@model)
@$(".dogear").addClass("is-followed")
renderVoted: =>
if window.user.voted(@model)
@$("[data-role=discussion-vote]").addClass("is-cast")
else
@$("[data-role=discussion-vote]").removeClass("is-cast")
updateModelDetails: =>
@renderVoted()
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"])
convertMath: ->
element = @$(".post-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: ->
DiscussionUtil.safeAjax
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
$loading: @$el
success: (data, textStatus, xhr) =>
@$el.find(".loading").remove()
Content.loadContentInfos(data['annotated_content_info'])
comments = new Comments(data['content']['children'])
comments.each @renderResponse
@trigger "thread:responses:rendered"
renderResponse: (response) =>
response.set('thread', @model)
view = new ThreadResponseView(model: response)
view.on "comment:add", @addComment
view.render()
@$el.find(".responses").append(view.el)
addComment: =>
@model.comment()
toggleVote: (event) ->
event.preventDefault()
if window.user.voted(@model)
@unvote()
else
@vote()
toggleFollowing: (event) ->
$elem = $(event.target)
url = null
console.log "follow"
if not @model.get('subscribed')
@model.follow()
url = @model.urlFor("follow")
else
@model.unfollow()
url = @model.urlFor("unfollow")
DiscussionUtil.safeAjax
$elem: $elem
url: url
type: "POST"
vote: ->
window.user.vote(@model)
url = @model.urlFor("upvote")
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
success: (response, textStatus) =>
if textStatus == 'success'
@model.set(response)
unvote: ->
window.user.unvote(@model)
url = @model.urlFor("unvote")
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
success: (response, textStatus) =>
if textStatus == 'success'
@model.set(response)
edit: ->
abbreviateBody: ->
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
@model.set('abbreviatedBody', abbreviated)
expandPost: (event) ->
@expanded = true
@$el.addClass('expanded')
@$el.find('.post-body').html(@model.get('body'))
@convertMath()
@$el.find('.expand-post').css('display', 'none')
@$el.find('.collapse-post').css('display', 'block')
@$el.find('.post-extended-content').show()
if @$el.find('.loading').length
@renderResponses()
collapsePost: (event) ->
@expanded = false
@$el.removeClass('expanded')
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
@convertMath()
@$el.find('.collapse-post').css('display', 'none')
@$el.find('.post-extended-content').hide()
@$el.find('.expand-post').css('display', 'block')
if Backbone?
class @DiscussionUserProfileView extends Backbone.View
# events:
# "":""
initialize: (options) ->
@renderThreads @$el, @collection
renderThreads: ($elem, threads) =>
#Content.loadContentInfos(response.annotated_content_info)
console.log threads
@discussion = new Discussion()
@discussion.reset(threads, {silent: false})
$discussion = $(Mustache.render $("script#_user_profile").html(), {'threads':threads})
console.log $discussion
$elem.append($discussion)
@threadviews = @discussion.map (thread) ->
new DiscussionThreadProfileView el: @$("article#thread_#{thread.id}"), model: thread
console.log @threadviews
_.each @threadviews, (dtv) -> dtv.render()
addThread: (thread, collection, options) =>
# TODO: When doing pagination, this will need to repaginate. Perhaps just reload page 1?
article = $("<article class='discussion-thread' id='thread_#{thread.id}'></article>")
@$('section.discussion > .threads').prepend(article)
threadView = new DiscussionThreadInlineView el: article, model: thread
threadView.render()
@threadviews.unshift threadView
...@@ -593,7 +593,7 @@ body.discussion { ...@@ -593,7 +593,7 @@ body.discussion {
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 0px 0px; background-position: 0px 0px;
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
.wmd-spacer1 { .wmd-spacer1 {
...@@ -1782,7 +1782,7 @@ body.discussion { ...@@ -1782,7 +1782,7 @@ body.discussion {
display: none; display: none;
} }
} }
} }
} }
...@@ -2087,7 +2087,7 @@ body.discussion { ...@@ -2087,7 +2087,7 @@ body.discussion {
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 0px 0px; background-position: 0px 0px;
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
.wmd-spacer1 { .wmd-spacer1 {
...@@ -2142,7 +2142,7 @@ body.discussion { ...@@ -2142,7 +2142,7 @@ body.discussion {
.wmd-button-row { .wmd-button-row {
// this is being hidden now because the inline styles to position the icons are not being written // this is being hidden now because the inline styles to position the icons are not being written
position: relative; position: relative;
height: 12px; height: 12px;
} }
.wmd-button { .wmd-button {
...@@ -2166,3 +2166,7 @@ body.discussion { ...@@ -2166,3 +2166,7 @@ body.discussion {
left: 300px; left: 300px;
} }
} }
.discussion-user-threads {
@extend .discussion-module
}
<article class="discussion-article" data-id="{{id}}">
<div class="local"><a href="javascript:void(0)" class="dogear action-follow"></a></div>
<div class="discussion-post local">
<header>
<a href="#" class="vote-btn discussion-vote discussion-vote-up" data-role="discussion-vote"><span class="plus-icon">+</span> <span class='votes-count-number'>{{votes.up_count}}</span></a>
<h3>{{title}}</h3>
<p class="posted-details">
<span class="timeago" title="{{created_at}}">{{created_at}}</span> by
{{#user}}
<a href="{{user_url}}">{{username}}</a>
{{/user}}
{{^user}}
anonymous
{{/user}}
<span class="post-status-closed top-post-status" style="display: none">
&bull; This thread is closed.
</span>
</p>
</header>
<div class="post-body">{{abbreviatedBody}}</div>
</div>
<ol class="responses post-extended-content">
<li class="loading"></li>
</ol>
<div class="local post-tools">
<a href="javascript:void(0)" class="expand-post">View discussion</a>
<a href="javascript:void(0)" class="collapse-post">Hide discussion</a>
</div>
</article>
<section class="discussion-user-threads" >
<section class="discussion">
{{#threads}}
<article class="discussion-thread" id="thread_{{id}}">
</article>
{{/threads}}
</div>
<section class="pagination">
</section>
</section>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<div class="course-wrapper"> <div class="course-wrapper">
<section aria-label="User Profile" class="user-profile"> <section aria-label="User Profile" class="user-profile">
<nav> <nav>
<article class="sidebar-module discussion-sidebar"> <article class="sidebar-module discussion-sidebar">
<%include file="_user_profile.html" /> <%include file="_user_profile.html" />
</article> </article>
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
</nav> </nav>
</section> </section>
<section class="course-content"> <section class="course-content container discussion-user-threads" data-user-id="${django_user.id | escapejs}" data-course-id="${course.id | escapejs}" data-threads="${threads}" data-user-info="${user_info}">
${content.decode('utf-8')} <h2>Active Threads</h2>
</section> </section>
</div> </div>
</section> </section>
...@@ -39,3 +39,4 @@ ...@@ -39,3 +39,4 @@
var $$profiled_user_id = "${django_user.id | escapejs}"; var $$profiled_user_id = "${django_user.id | escapejs}";
var $$course_id = "${course.id | escapejs}"; var $$course_id = "${course.id | escapejs}";
</script> </script>
<%include file="_underscore_templates.html" />
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