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.
This commit is contained in:
@@ -9,7 +9,7 @@ If you haven't done so already:
|
||||
brew install mongodb
|
||||
|
||||
Make sure that you have mongodb running. You can simply open a new terminal tab and type:
|
||||
|
||||
|
||||
mongod
|
||||
|
||||
## Installing elasticsearch
|
||||
@@ -72,9 +72,9 @@ For convenience, add the following environment variables to the terminal (assumi
|
||||
export DJANGO_SETTINGS_MODULE=lms.envs.dev
|
||||
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"):
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import xml.sax.saxutils as saxutils
|
||||
THREADS_PER_PAGE = 200
|
||||
INLINE_THREADS_PER_PAGE = 5
|
||||
PAGES_NEARBY_DELTA = 2
|
||||
|
||||
escapedict = {'"': '"'}
|
||||
log = logging.getLogger("edx.discussions")
|
||||
|
||||
def _general_discussion_id(course_id):
|
||||
@@ -83,7 +83,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
|
||||
thread['courseware_title'] = courseware_context['courseware_title']
|
||||
|
||||
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,
|
||||
'user_id': user_id,
|
||||
'course_id': course_id,
|
||||
@@ -94,7 +94,8 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
|
||||
'base_url': base_url,
|
||||
'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))),
|
||||
'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())
|
||||
return render_to_string(template, context)
|
||||
@@ -161,17 +162,12 @@ def inline_discussion(request, course_id, discussion_id):
|
||||
# checking for errors on request. Check and fix as needed.
|
||||
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):
|
||||
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
|
||||
|
||||
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
|
||||
|
||||
return utils.JsonResponse({
|
||||
# 'html': html,
|
||||
'discussion_data': map(utils.safe_content, threads),
|
||||
'user_info': user_info,
|
||||
'annotated_content_info': annotated_content_info,
|
||||
@@ -193,8 +189,6 @@ def forum_form_discussion(request, course_id):
|
||||
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
|
||||
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()
|
||||
|
||||
def infogetter(thread):
|
||||
@@ -208,8 +202,7 @@ def forum_form_discussion(request, course_id):
|
||||
thread['courseware_title'] = courseware_context['courseware_title']
|
||||
if request.is_ajax():
|
||||
return utils.JsonResponse({
|
||||
#'html': content,
|
||||
'discussion_data': threads,
|
||||
'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads'
|
||||
'annotated_content_info': annotated_content_info,
|
||||
})
|
||||
else:
|
||||
@@ -223,11 +216,9 @@ def forum_form_discussion(request, course_id):
|
||||
# course_id,
|
||||
#)
|
||||
|
||||
escapedict = {'"': '"'}
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
#'content': content,
|
||||
#'recent_active_threads': recent_active_threads,
|
||||
#'trending_tags': trending_tags,
|
||||
'staff_access' : has_access(request.user, course, 'staff'),
|
||||
@@ -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)
|
||||
context = {'thread': thread.to_dict(), 'course_id': course_id}
|
||||
# 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())
|
||||
if courseware_context:
|
||||
content.update(courseware_context)
|
||||
return utils.JsonResponse({
|
||||
'html': html,
|
||||
#'html': html,
|
||||
'content': content,
|
||||
'annotated_content_info': annotated_content_info,
|
||||
})
|
||||
@@ -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()
|
||||
escapedict = {'"': '"'}
|
||||
|
||||
|
||||
def infogetter(thread):
|
||||
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):
|
||||
context = {
|
||||
'discussion_id': discussion_id,
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'init': '',
|
||||
'init': '', #TODO: What is this?
|
||||
'user_info': saxutils.escape(json.dumps(user_info),escapedict),
|
||||
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
|
||||
'course': course,
|
||||
#'recent_active_threads': recent_active_threads,
|
||||
#'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,
|
||||
'threads': saxutils.escape(json.dumps(threads), escapedict),
|
||||
'category_map': category_map,
|
||||
@@ -323,11 +314,15 @@ def user_profile(request, course_id, user_id):
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
try:
|
||||
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['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():
|
||||
return utils.JsonResponse({
|
||||
@@ -335,12 +330,21 @@ def user_profile(request, course_id, user_id):
|
||||
'discussion_data': map(utils.safe_content, threads),
|
||||
})
|
||||
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 = {
|
||||
'course': course,
|
||||
'user': request.user,
|
||||
'django_user': User.objects.get(id=user_id),
|
||||
'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)
|
||||
|
||||
@@ -7,8 +7,10 @@ class Command(BaseCommand):
|
||||
help = 'Seed default permisssions and roles'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) != 1:
|
||||
raise CommandError("The number of arguments does not match. ")
|
||||
if len(args) == 0:
|
||||
raise CommandError("Please provide a course id")
|
||||
if len(args) > 1:
|
||||
raise CommandError("Too many arguments")
|
||||
course_id = args[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]
|
||||
|
||||
@@ -12,7 +12,16 @@ if Backbone?
|
||||
discussion = new Discussion(threads)
|
||||
new DiscussionRouter({discussion: discussion})
|
||||
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) ->
|
||||
DiscussionApp.start(elem)
|
||||
$("section.discussion-user-threads").each (index, elem) ->
|
||||
DiscussionProfileApp.start(elem)
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
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')
|
||||
@@ -0,0 +1,26 @@
|
||||
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 {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0px 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.wmd-spacer1 {
|
||||
@@ -1782,7 +1782,7 @@ body.discussion {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2087,7 +2087,7 @@ body.discussion {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0px 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.wmd-spacer1 {
|
||||
@@ -2142,7 +2142,7 @@ body.discussion {
|
||||
.wmd-button-row {
|
||||
// this is being hidden now because the inline styles to position the icons are not being written
|
||||
position: relative;
|
||||
height: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
@@ -2166,3 +2166,7 @@ body.discussion {
|
||||
left: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-user-threads {
|
||||
@extend .discussion-module
|
||||
}
|
||||
|
||||
31
lms/templates/discussion/mustache/_profile_thread.mustache
Normal file
31
lms/templates/discussion/mustache/_profile_thread.mustache
Normal file
@@ -0,0 +1,31 @@
|
||||
<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">
|
||||
• 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>
|
||||
10
lms/templates/discussion/mustache/_user_profile.mustache
Normal file
10
lms/templates/discussion/mustache/_user_profile.mustache
Normal file
@@ -0,0 +1,10 @@
|
||||
<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 @@
|
||||
<div class="course-wrapper">
|
||||
<section aria-label="User Profile" class="user-profile">
|
||||
<nav>
|
||||
|
||||
|
||||
<article class="sidebar-module discussion-sidebar">
|
||||
<%include file="_user_profile.html" />
|
||||
</article>
|
||||
@@ -29,8 +29,8 @@
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="course-content">
|
||||
${content.decode('utf-8')}
|
||||
<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}">
|
||||
<h2>Active Threads</h2>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
@@ -39,3 +39,4 @@
|
||||
var $$profiled_user_id = "${django_user.id | escapejs}";
|
||||
var $$course_id = "${course.id | escapejs}";
|
||||
</script>
|
||||
<%include file="_underscore_templates.html" />
|
||||
|
||||
Reference in New Issue
Block a user