Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
182ae7ae
Commit
182ae7ae
authored
Jul 21, 2014
by
Greg Price
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add endorsement info to marked answers in forum
Co-authored-by: jsa <jsa@edx.org>
parent
10063a31
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
178 additions
and
56 deletions
+178
-56
common/static/coffee/spec/discussion/view/thread_response_show_view_spec.coffee
+63
-4
common/static/coffee/src/discussion/views/thread_response_show_view.coffee
+1
-1
lms/djangoapps/django_comment_client/base/views.py
+50
-35
lms/djangoapps/django_comment_client/forum/views.py
+6
-6
lms/djangoapps/django_comment_client/utils.py
+30
-4
lms/lib/comment_client/comment.py
+2
-2
lms/lib/comment_client/models.py
+2
-2
lms/static/sass/discussion/_discussion.scss
+2
-1
lms/templates/discussion/_underscore_templates.html
+22
-1
No files found.
common/static/coffee/spec/discussion/view/thread_response_show_view_spec.coffee
View file @
182ae7ae
...
@@ -3,14 +3,38 @@ describe "ThreadResponseShowView", ->
...
@@ -3,14 +3,38 @@ describe "ThreadResponseShowView", ->
DiscussionSpecHelper
.
setUpGlobals
()
DiscussionSpecHelper
.
setUpGlobals
()
setFixtures
(
setFixtures
(
"""
"""
<div class="discussion-post">
<script type="text/template" id="thread-response-show-template">
<a href="#" class="vote-btn" data-tooltip="vote" role="button" aria-pressed="false">
<a href="#" class="vote-btn" data-tooltip="vote" role="button" aria-pressed="false"></a>
<span class="plus-icon"/><span class="votes-count-number">0</span> <span class="sr">votes (click to vote)</span>
<a
href="javascript:void(0)"
class="endorse-btn action-endorse <%= thread.get('thread_type') == 'question' ? 'mark-answer' : '' %>"
style="cursor: default; display: none;"
data-tooltip="<%= thread.get('thread_type') == 'question' ? 'mark as answer' : 'endorse' %>"
>
<span class="check-icon" style="pointer-events: none; "></span>
</a>
</a>
</div>
<p class="posted-details">
<span class="timeago" title="<%= created_at %>"><%= created_at %></span>
<% if (thread.get('thread_type') == 'question' && obj.endorsement) { %> -
<%=
interpolate(
endorsement.username ? "marked as answer %(time_ago)s by %(user)s" : "marked as answer %(time_ago)s",
{
'time_ago': '<span class="timeago" title="' + endorsement.time + '">' + endorsement.time + '</span>',
'user': endorsement.username
},
true
)
%>
<% } %>
</p>
</script>
<div class="discussion-post"></div>
"""
"""
)
)
@
thread
=
new
Thread
({
"thread_type"
:
"discussion"
})
@
commentData
=
{
@
commentData
=
{
id
:
"dummy"
,
id
:
"dummy"
,
user_id
:
"567"
,
user_id
:
"567"
,
...
@@ -21,9 +45,15 @@ describe "ThreadResponseShowView", ->
...
@@ -21,9 +45,15 @@ describe "ThreadResponseShowView", ->
votes
:
{
up_count
:
"42"
}
votes
:
{
up_count
:
"42"
}
}
}
@
comment
=
new
Comment
(
@
commentData
)
@
comment
=
new
Comment
(
@
commentData
)
@
comment
.
set
(
"thread"
,
@
thread
)
@
view
=
new
ThreadResponseShowView
({
model
:
@
comment
})
@
view
=
new
ThreadResponseShowView
({
model
:
@
comment
})
@
view
.
setElement
(
$
(
".discussion-post"
))
@
view
.
setElement
(
$
(
".discussion-post"
))
# Avoid unnecessary boilerplate
spyOn
(
ThreadResponseShowView
.
prototype
,
"convertMath"
)
@
view
.
render
()
it
"renders the vote correctly"
,
->
it
"renders the vote correctly"
,
->
DiscussionViewSpecHelper
.
checkRenderVote
(
@
view
,
@
comment
)
DiscussionViewSpecHelper
.
checkRenderVote
(
@
view
,
@
comment
)
...
@@ -38,3 +68,32 @@ describe "ThreadResponseShowView", ->
...
@@ -38,3 +68,32 @@ describe "ThreadResponseShowView", ->
it
"vote button activates on appropriate events"
,
->
it
"vote button activates on appropriate events"
,
->
DiscussionViewSpecHelper
.
checkVoteButtonEvents
(
@
view
)
DiscussionViewSpecHelper
.
checkVoteButtonEvents
(
@
view
)
it
"renders endorsement correctly for a marked answer in a question thread"
,
->
endorsement
=
{
"username"
:
"test_endorser"
,
"time"
:
new
Date
().
toISOString
()
}
@
thread
.
set
(
"thread_type"
,
"question"
)
@
comment
.
set
({
"endorsed"
:
true
,
"endorsement"
:
endorsement
})
@
view
.
render
()
expect
(
@
view
.
$
(
".posted-details"
).
text
()).
toMatch
(
"marked as answer less than a minute ago by "
+
endorsement
.
username
)
it
"renders anonymous endorsement correctly for a marked answer in a question thread"
,
->
endorsement
=
{
"username"
:
null
,
"time"
:
new
Date
().
toISOString
()
}
@
thread
.
set
(
"thread_type"
,
"question"
)
@
comment
.
set
({
"endorsed"
:
true
,
"endorsement"
:
endorsement
})
@
view
.
render
()
expect
(
@
view
.
$
(
".posted-details"
).
text
()).
toMatch
(
"marked as answer less than a minute ago"
)
expect
(
@
view
.
$
(
".posted-details"
).
text
()).
not
.
toMatch
(
" by "
)
common/static/coffee/src/discussion/views/thread_response_show_view.coffee
View file @
182ae7ae
...
@@ -29,7 +29,7 @@ if Backbone?
...
@@ -29,7 +29,7 @@ if Backbone?
@
renderVote
()
@
renderVote
()
@
renderAttrs
()
@
renderAttrs
()
@
renderFlagged
()
@
renderFlagged
()
@
$el
.
find
(
".posted-details"
).
timeago
()
@
$el
.
find
(
".posted-details
.timeago
"
).
timeago
()
@
convertMath
()
@
convertMath
()
@
markAsStaff
()
@
markAsStaff
()
@
@
...
...
lms/djangoapps/django_comment_client/base/views.py
View file @
182ae7ae
...
@@ -53,11 +53,11 @@ def permitted(fn):
...
@@ -53,11 +53,11 @@ def permitted(fn):
return
wrapper
return
wrapper
def
ajax_content_response
(
request
,
course_
id
,
content
):
def
ajax_content_response
(
request
,
course_
key
,
content
):
user_info
=
cc
.
User
.
from_django_user
(
request
.
user
)
.
to_dict
()
user_info
=
cc
.
User
.
from_django_user
(
request
.
user
)
.
to_dict
()
annotated_content_info
=
get_annotated_content_info
(
course_
id
,
content
,
request
.
user
,
user_info
)
annotated_content_info
=
get_annotated_content_info
(
course_
key
,
content
,
request
.
user
,
user_info
)
return
JsonResponse
({
return
JsonResponse
({
'content'
:
safe_content
(
content
),
'content'
:
safe_content
(
content
,
course_key
),
'annotated_content_info'
:
annotated_content_info
,
'annotated_content_info'
:
annotated_content_info
,
})
})
...
@@ -71,8 +71,8 @@ def create_thread(request, course_id, commentable_id):
...
@@ -71,8 +71,8 @@ def create_thread(request, course_id, commentable_id):
"""
"""
log
.
debug
(
"Creating new thread in
%
r, id
%
r"
,
course_id
,
commentable_id
)
log
.
debug
(
"Creating new thread in
%
r, id
%
r"
,
course_id
,
commentable_id
)
course_
id
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
course_
key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
course
=
get_course_with_access
(
request
.
user
,
'load'
,
course_
id
)
course
=
get_course_with_access
(
request
.
user
,
'load'
,
course_
key
)
post
=
request
.
POST
post
=
request
.
POST
if
course
.
allow_anonymous
:
if
course
.
allow_anonymous
:
...
@@ -94,7 +94,7 @@ def create_thread(request, course_id, commentable_id):
...
@@ -94,7 +94,7 @@ def create_thread(request, course_id, commentable_id):
anonymous
=
anonymous
,
anonymous
=
anonymous
,
anonymous_to_peers
=
anonymous_to_peers
,
anonymous_to_peers
=
anonymous_to_peers
,
commentable_id
=
commentable_id
,
commentable_id
=
commentable_id
,
course_id
=
course_
id
.
to_deprecated_string
(),
course_id
=
course_
key
.
to_deprecated_string
(),
user_id
=
request
.
user
.
id
,
user_id
=
request
.
user
.
id
,
body
=
post
[
"body"
],
body
=
post
[
"body"
],
title
=
post
[
"title"
]
title
=
post
[
"title"
]
...
@@ -107,13 +107,13 @@ def create_thread(request, course_id, commentable_id):
...
@@ -107,13 +107,13 @@ def create_thread(request, course_id, commentable_id):
#not anymore, only for admins
#not anymore, only for admins
# Cohort the thread if the commentable is cohorted.
# Cohort the thread if the commentable is cohorted.
if
is_commentable_cohorted
(
course_
id
,
commentable_id
):
if
is_commentable_cohorted
(
course_
key
,
commentable_id
):
user_group_id
=
get_cohort_id
(
user
,
course_
id
)
user_group_id
=
get_cohort_id
(
user
,
course_
key
)
# TODO (vshnayder): once we have more than just cohorts, we'll want to
# TODO (vshnayder): once we have more than just cohorts, we'll want to
# change this to a single get_group_for_user_and_commentable function
# change this to a single get_group_for_user_and_commentable function
# that can do different things depending on the commentable_id
# that can do different things depending on the commentable_id
if
cached_has_permission
(
request
.
user
,
"see_all_cohorts"
,
course_
id
):
if
cached_has_permission
(
request
.
user
,
"see_all_cohorts"
,
course_
key
):
# admins can optionally choose what group to post as
# admins can optionally choose what group to post as
group_id
=
post
.
get
(
'group_id'
,
user_group_id
)
group_id
=
post
.
get
(
'group_id'
,
user_group_id
)
else
:
else
:
...
@@ -135,9 +135,9 @@ def create_thread(request, course_id, commentable_id):
...
@@ -135,9 +135,9 @@ def create_thread(request, course_id, commentable_id):
data
=
thread
.
to_dict
()
data
=
thread
.
to_dict
()
add_courseware_context
([
data
],
course
)
add_courseware_context
([
data
],
course
)
if
request
.
is_ajax
():
if
request
.
is_ajax
():
return
ajax_content_response
(
request
,
course_
id
,
data
)
return
ajax_content_response
(
request
,
course_
key
,
data
)
else
:
else
:
return
JsonResponse
(
safe_content
(
data
))
return
JsonResponse
(
safe_content
(
data
,
course_key
))
@require_POST
@require_POST
...
@@ -151,19 +151,20 @@ def update_thread(request, course_id, thread_id):
...
@@ -151,19 +151,20 @@ def update_thread(request, course_id, thread_id):
return
JsonError
(
_
(
"Title can't be empty"
))
return
JsonError
(
_
(
"Title can't be empty"
))
if
'body'
not
in
request
.
POST
or
not
request
.
POST
[
'body'
]
.
strip
():
if
'body'
not
in
request
.
POST
or
not
request
.
POST
[
'body'
]
.
strip
():
return
JsonError
(
_
(
"Body can't be empty"
))
return
JsonError
(
_
(
"Body can't be empty"
))
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
.
body
=
request
.
POST
[
"body"
]
thread
.
body
=
request
.
POST
[
"body"
]
thread
.
title
=
request
.
POST
[
"title"
]
thread
.
title
=
request
.
POST
[
"title"
]
thread
.
save
()
thread
.
save
()
if
request
.
is_ajax
():
if
request
.
is_ajax
():
return
ajax_content_response
(
request
,
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
,
thread
.
to_dict
())
return
ajax_content_response
(
request
,
course_key
,
thread
.
to_dict
())
else
:
else
:
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
def
_create_comment
(
request
,
course_key
,
thread_id
=
None
,
parent_id
=
None
):
def
_create_comment
(
request
,
course_key
,
thread_id
=
None
,
parent_id
=
None
):
"""
"""
given a course_
id
, thread_id, and parent_id, create a comment,
given a course_
key
, thread_id, and parent_id, create a comment,
called from create_comment to do the actual creation
called from create_comment to do the actual creation
"""
"""
assert
isinstance
(
course_key
,
CourseKey
)
assert
isinstance
(
course_key
,
CourseKey
)
...
@@ -199,7 +200,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None):
...
@@ -199,7 +200,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None):
if
request
.
is_ajax
():
if
request
.
is_ajax
():
return
ajax_content_response
(
request
,
course_key
,
comment
.
to_dict
())
return
ajax_content_response
(
request
,
course_key
,
comment
.
to_dict
())
else
:
else
:
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course
.
id
))
@require_POST
@require_POST
...
@@ -224,9 +225,10 @@ def delete_thread(request, course_id, thread_id):
...
@@ -224,9 +225,10 @@ def delete_thread(request, course_id, thread_id):
given a course_id and thread_id, delete this thread
given a course_id and thread_id, delete this thread
this is ajax only
this is ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
.
delete
()
thread
.
delete
()
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -237,15 +239,16 @@ def update_comment(request, course_id, comment_id):
...
@@ -237,15 +239,16 @@ def update_comment(request, course_id, comment_id):
given a course_id and comment_id, update the comment with payload attributes
given a course_id and comment_id, update the comment with payload attributes
handles static and ajax submissions
handles static and ajax submissions
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
if
'body'
not
in
request
.
POST
or
not
request
.
POST
[
'body'
]
.
strip
():
if
'body'
not
in
request
.
POST
or
not
request
.
POST
[
'body'
]
.
strip
():
return
JsonError
(
_
(
"Body can't be empty"
))
return
JsonError
(
_
(
"Body can't be empty"
))
comment
.
body
=
request
.
POST
[
"body"
]
comment
.
body
=
request
.
POST
[
"body"
]
comment
.
save
()
comment
.
save
()
if
request
.
is_ajax
():
if
request
.
is_ajax
():
return
ajax_content_response
(
request
,
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
,
comment
.
to_dict
())
return
ajax_content_response
(
request
,
course_key
,
comment
.
to_dict
())
else
:
else
:
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -256,10 +259,12 @@ def endorse_comment(request, course_id, comment_id):
...
@@ -256,10 +259,12 @@ def endorse_comment(request, course_id, comment_id):
given a course_id and comment_id, toggle the endorsement of this comment,
given a course_id and comment_id, toggle the endorsement of this comment,
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
.
endorsed
=
request
.
POST
.
get
(
'endorsed'
,
'false'
)
.
lower
()
==
'true'
comment
.
endorsed
=
request
.
POST
.
get
(
'endorsed'
,
'false'
)
.
lower
()
==
'true'
comment
.
endorsement_user_id
=
request
.
user
.
id
comment
.
save
()
comment
.
save
()
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -270,13 +275,14 @@ def openclose_thread(request, course_id, thread_id):
...
@@ -270,13 +275,14 @@ def openclose_thread(request, course_id, thread_id):
given a course_id and thread_id, toggle the status of this thread
given a course_id and thread_id, toggle the status of this thread
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
.
closed
=
request
.
POST
.
get
(
'closed'
,
'false'
)
.
lower
()
==
'true'
thread
.
closed
=
request
.
POST
.
get
(
'closed'
,
'false'
)
.
lower
()
==
'true'
thread
.
save
()
thread
.
save
()
thread
=
thread
.
to_dict
()
thread
=
thread
.
to_dict
()
return
JsonResponse
({
return
JsonResponse
({
'content'
:
safe_content
(
thread
),
'content'
:
safe_content
(
thread
,
course_key
),
'ability'
:
get_ability
(
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
,
thread
,
request
.
user
),
'ability'
:
get_ability
(
course_key
,
thread
,
request
.
user
),
})
})
...
@@ -302,9 +308,10 @@ def delete_comment(request, course_id, comment_id):
...
@@ -302,9 +308,10 @@ def delete_comment(request, course_id, comment_id):
given a course_id and comment_id delete this comment
given a course_id and comment_id delete this comment
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
.
delete
()
comment
.
delete
()
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -314,10 +321,11 @@ def vote_for_comment(request, course_id, comment_id, value):
...
@@ -314,10 +321,11 @@ def vote_for_comment(request, course_id, comment_id, value):
"""
"""
given a course_id and comment_id,
given a course_id and comment_id,
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
user
.
vote
(
comment
,
value
)
user
.
vote
(
comment
,
value
)
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -328,10 +336,11 @@ def undo_vote_for_comment(request, course_id, comment_id):
...
@@ -328,10 +336,11 @@ def undo_vote_for_comment(request, course_id, comment_id):
given a course id and comment id, remove vote
given a course id and comment id, remove vote
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
user
.
unvote
(
comment
)
user
.
unvote
(
comment
)
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -342,10 +351,11 @@ def vote_for_thread(request, course_id, thread_id, value):
...
@@ -342,10 +351,11 @@ def vote_for_thread(request, course_id, thread_id, value):
given a course id and thread id vote for this thread
given a course id and thread id vote for this thread
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
user
.
vote
(
thread
,
value
)
user
.
vote
(
thread
,
value
)
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -356,10 +366,11 @@ def flag_abuse_for_thread(request, course_id, thread_id):
...
@@ -356,10 +366,11 @@ def flag_abuse_for_thread(request, course_id, thread_id):
given a course_id and thread_id flag this thread for abuse
given a course_id and thread_id flag this thread for abuse
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
.
flagAbuse
(
user
,
thread
)
thread
.
flagAbuse
(
user
,
thread
)
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -371,12 +382,12 @@ def un_flag_abuse_for_thread(request, course_id, thread_id):
...
@@ -371,12 +382,12 @@ def un_flag_abuse_for_thread(request, course_id, thread_id):
ajax only
ajax only
"""
"""
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
course_
id
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
course_
key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
course
=
get_course_by_id
(
course_
id
)
course
=
get_course_by_id
(
course_
key
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
remove_all
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course_
id
)
or
has_access
(
request
.
user
,
'staff'
,
course
)
remove_all
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course_
key
)
or
has_access
(
request
.
user
,
'staff'
,
course
)
thread
.
unFlagAbuse
(
user
,
thread
,
remove_all
)
thread
.
unFlagAbuse
(
user
,
thread
,
remove_all
)
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -387,10 +398,11 @@ def flag_abuse_for_comment(request, course_id, comment_id):
...
@@ -387,10 +398,11 @@ def flag_abuse_for_comment(request, course_id, comment_id):
given a course and comment id, flag comment for abuse
given a course and comment id, flag comment for abuse
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
.
flagAbuse
(
user
,
comment
)
comment
.
flagAbuse
(
user
,
comment
)
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -407,7 +419,7 @@ def un_flag_abuse_for_comment(request, course_id, comment_id):
...
@@ -407,7 +419,7 @@ def un_flag_abuse_for_comment(request, course_id, comment_id):
remove_all
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course_key
)
or
has_access
(
request
.
user
,
'staff'
,
course
)
remove_all
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course_key
)
or
has_access
(
request
.
user
,
'staff'
,
course
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
=
cc
.
Comment
.
find
(
comment_id
)
comment
.
unFlagAbuse
(
user
,
comment
,
remove_all
)
comment
.
unFlagAbuse
(
user
,
comment
,
remove_all
)
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()))
return
JsonResponse
(
safe_content
(
comment
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -418,10 +430,11 @@ def undo_vote_for_thread(request, course_id, thread_id):
...
@@ -418,10 +430,11 @@ def undo_vote_for_thread(request, course_id, thread_id):
given a course id and thread id, remove users vote for thread
given a course id and thread id, remove users vote for thread
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
user
.
unvote
(
thread
)
user
.
unvote
(
thread
)
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -432,10 +445,11 @@ def pin_thread(request, course_id, thread_id):
...
@@ -432,10 +445,11 @@ def pin_thread(request, course_id, thread_id):
given a course id and thread id, pin this thread
given a course id and thread id, pin this thread
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
.
pin
(
user
,
thread_id
)
thread
.
pin
(
user
,
thread_id
)
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
@@ -446,10 +460,11 @@ def un_pin_thread(request, course_id, thread_id):
...
@@ -446,10 +460,11 @@ def un_pin_thread(request, course_id, thread_id):
given a course id and thread id, remove pin from this thread
given a course id and thread id, remove pin from this thread
ajax only
ajax only
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
user
=
cc
.
User
.
from_django_user
(
request
.
user
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
=
cc
.
Thread
.
find
(
thread_id
)
thread
.
un_pin
(
user
,
thread_id
)
thread
.
un_pin
(
user
,
thread_id
)
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()))
return
JsonResponse
(
safe_content
(
thread
.
to_dict
()
,
course_key
))
@require_POST
@require_POST
...
...
lms/djangoapps/django_comment_client/forum/views.py
View file @
182ae7ae
...
@@ -150,7 +150,7 @@ def inline_discussion(request, course_id, discussion_id):
...
@@ -150,7 +150,7 @@ def inline_discussion(request, course_id, discussion_id):
annotated_content_info
=
utils
.
get_metadata_for_threads
(
course_id
,
threads
,
request
.
user
,
user_info
)
annotated_content_info
=
utils
.
get_metadata_for_threads
(
course_id
,
threads
,
request
.
user
,
user_info
)
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
return
utils
.
JsonResponse
({
return
utils
.
JsonResponse
({
'discussion_data'
:
[
utils
.
safe_content
(
thread
,
is_staff
)
for
thread
in
threads
],
'discussion_data'
:
[
utils
.
safe_content
(
thread
,
course_id
,
is_staff
)
for
thread
in
threads
],
'user_info'
:
user_info
,
'user_info'
:
user_info
,
'annotated_content_info'
:
annotated_content_info
,
'annotated_content_info'
:
annotated_content_info
,
'page'
:
query_params
[
'page'
],
'page'
:
query_params
[
'page'
],
...
@@ -173,7 +173,7 @@ def forum_form_discussion(request, course_id):
...
@@ -173,7 +173,7 @@ def forum_form_discussion(request, course_id):
try
:
try
:
unsafethreads
,
query_params
=
get_threads
(
request
,
course_id
)
# This might process a search query
unsafethreads
,
query_params
=
get_threads
(
request
,
course_id
)
# This might process a search query
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
threads
=
[
utils
.
safe_content
(
thread
,
is_staff
)
for
thread
in
unsafethreads
]
threads
=
[
utils
.
safe_content
(
thread
,
course_id
,
is_staff
)
for
thread
in
unsafethreads
]
except
cc
.
utils
.
CommentClientMaintenanceError
:
except
cc
.
utils
.
CommentClientMaintenanceError
:
log
.
warning
(
"Forum is in maintenance mode"
)
log
.
warning
(
"Forum is in maintenance mode"
)
return
render_to_response
(
'discussion/maintenance.html'
,
{})
return
render_to_response
(
'discussion/maintenance.html'
,
{})
...
@@ -253,7 +253,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
...
@@ -253,7 +253,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if
request
.
is_ajax
():
if
request
.
is_ajax
():
with
newrelic
.
agent
.
FunctionTrace
(
nr_transaction
,
"get_annotated_content_infos"
):
with
newrelic
.
agent
.
FunctionTrace
(
nr_transaction
,
"get_annotated_content_infos"
):
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
)
content
=
utils
.
safe_content
(
thread
.
to_dict
(),
is_staff
)
content
=
utils
.
safe_content
(
thread
.
to_dict
(),
course_id
,
is_staff
)
with
newrelic
.
agent
.
FunctionTrace
(
nr_transaction
,
"add_courseware_context"
):
with
newrelic
.
agent
.
FunctionTrace
(
nr_transaction
,
"add_courseware_context"
):
add_courseware_context
([
content
],
course
)
add_courseware_context
([
content
],
course
)
return
utils
.
JsonResponse
({
return
utils
.
JsonResponse
({
...
@@ -276,7 +276,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
...
@@ -276,7 +276,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if
not
"pinned"
in
thread
:
if
not
"pinned"
in
thread
:
thread
[
"pinned"
]
=
False
thread
[
"pinned"
]
=
False
threads
=
[
utils
.
safe_content
(
thread
,
is_staff
)
for
thread
in
threads
]
threads
=
[
utils
.
safe_content
(
thread
,
course_id
,
is_staff
)
for
thread
in
threads
]
with
newrelic
.
agent
.
FunctionTrace
(
nr_transaction
,
"get_metadata_for_threads"
):
with
newrelic
.
agent
.
FunctionTrace
(
nr_transaction
,
"get_metadata_for_threads"
):
annotated_content_info
=
utils
.
get_metadata_for_threads
(
course_id
,
threads
,
request
.
user
,
user_info
)
annotated_content_info
=
utils
.
get_metadata_for_threads
(
course_id
,
threads
,
request
.
user
,
user_info
)
...
@@ -335,7 +335,7 @@ def user_profile(request, course_id, user_id):
...
@@ -335,7 +335,7 @@ def user_profile(request, course_id, user_id):
if
request
.
is_ajax
():
if
request
.
is_ajax
():
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
return
utils
.
JsonResponse
({
return
utils
.
JsonResponse
({
'discussion_data'
:
[
utils
.
safe_content
(
thread
,
is_staff
)
for
thread
in
threads
],
'discussion_data'
:
[
utils
.
safe_content
(
thread
,
course_id
,
is_staff
)
for
thread
in
threads
],
'page'
:
query_params
[
'page'
],
'page'
:
query_params
[
'page'
],
'num_pages'
:
query_params
[
'num_pages'
],
'num_pages'
:
query_params
[
'num_pages'
],
'annotated_content_info'
:
_attr_safe_json
(
annotated_content_info
),
'annotated_content_info'
:
_attr_safe_json
(
annotated_content_info
),
...
@@ -386,7 +386,7 @@ def followed_threads(request, course_id, user_id):
...
@@ -386,7 +386,7 @@ def followed_threads(request, course_id, user_id):
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
is_staff
=
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
return
utils
.
JsonResponse
({
return
utils
.
JsonResponse
({
'annotated_content_info'
:
annotated_content_info
,
'annotated_content_info'
:
annotated_content_info
,
'discussion_data'
:
[
utils
.
safe_content
(
thread
,
is_staff
)
for
thread
in
threads
],
'discussion_data'
:
[
utils
.
safe_content
(
thread
,
course_id
,
is_staff
)
for
thread
in
threads
],
'page'
:
query_params
[
'page'
],
'page'
:
query_params
[
'page'
],
'num_pages'
:
query_params
[
'num_pages'
],
'num_pages'
:
query_params
[
'num_pages'
],
})
})
...
...
lms/djangoapps/django_comment_client/utils.py
View file @
182ae7ae
...
@@ -9,7 +9,7 @@ from django.db import connection
...
@@ -9,7 +9,7 @@ from django.db import connection
from
django.http
import
HttpResponse
from
django.http
import
HttpResponse
from
django.utils
import
simplejson
from
django.utils
import
simplejson
from
django_comment_common.models
import
Role
,
FORUM_ROLE_STUDENT
from
django_comment_common.models
import
Role
,
FORUM_ROLE_STUDENT
from
django_comment_client.permissions
import
check_permissions_by_view
from
django_comment_client.permissions
import
check_permissions_by_view
,
cached_has_permission
from
edxmako
import
lookup_template
from
edxmako
import
lookup_template
import
pystache_custom
as
pystache
import
pystache_custom
as
pystache
...
@@ -365,7 +365,7 @@ def add_courseware_context(content_list, course):
...
@@ -365,7 +365,7 @@ def add_courseware_context(content_list, course):
content
.
update
({
"courseware_url"
:
url
,
"courseware_title"
:
title
})
content
.
update
({
"courseware_url"
:
url
,
"courseware_title"
:
title
})
def
safe_content
(
content
,
is_staff
=
False
):
def
safe_content
(
content
,
course_id
,
is_staff
=
False
):
fields
=
[
fields
=
[
'id'
,
'title'
,
'body'
,
'course_id'
,
'anonymous'
,
'anonymous_to_peers'
,
'id'
,
'title'
,
'body'
,
'course_id'
,
'anonymous'
,
'anonymous_to_peers'
,
'endorsed'
,
'parent_id'
,
'thread_id'
,
'votes'
,
'closed'
,
'created_at'
,
'endorsed'
,
'parent_id'
,
'thread_id'
,
'votes'
,
'closed'
,
'created_at'
,
...
@@ -375,14 +375,40 @@ def safe_content(content, is_staff=False):
...
@@ -375,14 +375,40 @@ def safe_content(content, is_staff=False):
'read'
,
'group_id'
,
'group_name'
,
'group_string'
,
'pinned'
,
'abuse_flaggers'
,
'read'
,
'group_id'
,
'group_name'
,
'group_string'
,
'pinned'
,
'abuse_flaggers'
,
'stats'
,
'resp_skip'
,
'resp_limit'
,
'resp_total'
,
'thread_type'
,
'stats'
,
'resp_skip'
,
'resp_limit'
,
'resp_total'
,
'thread_type'
,
'endorsed_responses'
,
'non_endorsed_responses'
,
'non_endorsed_resp_total'
,
'endorsed_responses'
,
'non_endorsed_responses'
,
'non_endorsed_resp_total'
,
'endorsement'
,
]
]
if
(
content
.
get
(
'anonymous'
)
is
False
)
and
((
content
.
get
(
'anonymous_to_peers'
)
is
False
)
or
is_staff
):
if
(
content
.
get
(
'anonymous'
)
is
False
)
and
((
content
.
get
(
'anonymous_to_peers'
)
is
False
)
or
is_staff
):
fields
+=
[
'username'
,
'user_id'
]
fields
+=
[
'username'
,
'user_id'
]
content
=
strip_none
(
extract
(
content
,
fields
))
if
content
.
get
(
"endorsement"
):
endorsement
=
content
[
"endorsement"
]
endorser
=
None
if
endorsement
[
"user_id"
]:
try
:
endorser
=
User
.
objects
.
get
(
pk
=
endorsement
[
"user_id"
])
except
User
.
DoesNotExist
:
log
.
error
(
"User ID {0} in endorsement for comment {1} but not in our DB."
.
format
(
content
.
get
(
'user_id'
),
content
.
get
(
'id'
))
)
# Only reveal endorser if requester can see author or if endorser is staff
if
(
endorser
and
(
"username"
in
fields
or
cached_has_permission
(
endorser
,
"endorse_comment"
,
course_id
))
):
endorsement
[
"username"
]
=
endorser
.
username
else
:
del
endorsement
[
"user_id"
]
for
child_content_key
in
[
"children"
,
"endorsed_responses"
,
"non_endorsed_responses"
]:
for
child_content_key
in
[
"children"
,
"endorsed_responses"
,
"non_endorsed_responses"
]:
if
child_content_key
in
content
:
if
child_content_key
in
content
:
safe_children
=
[
safe_content
(
child
)
for
child
in
content
[
child_content_key
]]
safe_children
=
[
safe_content
(
child
,
course_id
,
is_staff
)
for
child
in
content
[
child_content_key
]
]
content
[
child_content_key
]
=
safe_children
content
[
child_content_key
]
=
safe_children
return
strip_none
(
extract
(
content
,
fields
))
return
content
lms/lib/comment_client/comment.py
View file @
182ae7ae
...
@@ -11,12 +11,12 @@ class Comment(models.Model):
...
@@ -11,12 +11,12 @@ class Comment(models.Model):
'id'
,
'body'
,
'anonymous'
,
'anonymous_to_peers'
,
'course_id'
,
'id'
,
'body'
,
'anonymous'
,
'anonymous_to_peers'
,
'course_id'
,
'endorsed'
,
'parent_id'
,
'thread_id'
,
'username'
,
'votes'
,
'user_id'
,
'endorsed'
,
'parent_id'
,
'thread_id'
,
'username'
,
'votes'
,
'user_id'
,
'closed'
,
'created_at'
,
'updated_at'
,
'depth'
,
'at_position_list'
,
'closed'
,
'created_at'
,
'updated_at'
,
'depth'
,
'at_position_list'
,
'type'
,
'commentable_id'
,
'abuse_flaggers'
'type'
,
'commentable_id'
,
'abuse_flaggers'
,
'endorsement'
,
]
]
updatable_fields
=
[
updatable_fields
=
[
'body'
,
'anonymous'
,
'anonymous_to_peers'
,
'course_id'
,
'closed'
,
'body'
,
'anonymous'
,
'anonymous_to_peers'
,
'course_id'
,
'closed'
,
'user_id'
,
'endorsed'
'user_id'
,
'endorsed'
,
'endorsement_user_id'
,
]
]
initializable_fields
=
updatable_fields
initializable_fields
=
updatable_fields
...
...
lms/lib/comment_client/models.py
View file @
182ae7ae
...
@@ -35,7 +35,7 @@ class Model(object):
...
@@ -35,7 +35,7 @@ class Model(object):
return
self
.
__getattr__
(
name
)
return
self
.
__getattr__
(
name
)
def
__setattr__
(
self
,
name
,
value
):
def
__setattr__
(
self
,
name
,
value
):
if
name
==
'attributes'
or
name
not
in
self
.
accessible_fields
:
if
name
==
'attributes'
or
name
not
in
(
self
.
accessible_fields
+
self
.
updatable_fields
)
:
super
(
Model
,
self
)
.
__setattr__
(
name
,
value
)
super
(
Model
,
self
)
.
__setattr__
(
name
,
value
)
else
:
else
:
self
.
attributes
[
name
]
=
value
self
.
attributes
[
name
]
=
value
...
@@ -46,7 +46,7 @@ class Model(object):
...
@@ -46,7 +46,7 @@ class Model(object):
return
self
.
attributes
.
get
(
key
)
return
self
.
attributes
.
get
(
key
)
def
__setitem__
(
self
,
key
,
value
):
def
__setitem__
(
self
,
key
,
value
):
if
key
not
in
self
.
accessible_fields
:
if
key
not
in
(
self
.
accessible_fields
+
self
.
updatable_fields
)
:
raise
KeyError
(
"Field {0} does not exist"
.
format
(
key
))
raise
KeyError
(
"Field {0} does not exist"
.
format
(
key
))
self
.
attributes
.
__setitem__
(
key
,
value
)
self
.
attributes
.
__setitem__
(
key
,
value
)
...
...
lms/static/sass/discussion/_discussion.scss
View file @
182ae7ae
...
@@ -441,7 +441,8 @@ body.discussion {
...
@@ -441,7 +441,8 @@ body.discussion {
font-weight
:
700
;
font-weight
:
700
;
}
}
span
{
.timeago
,
.top-post-status
{
color
:
inherit
;
font-style
:
italic
;
font-style
:
italic
;
}
}
}
}
...
...
lms/templates/discussion/_underscore_templates.html
View file @
182ae7ae
...
@@ -159,7 +159,28 @@
...
@@ -159,7 +159,28 @@
$
{
"<% } else { %>"
}
$
{
"<% } else { %>"
}
<
span
class
=
"anonymous"
><
em
>
$
{
_
(
'anonymous'
)}
<
/em></
span
>
<
span
class
=
"anonymous"
><
em
>
$
{
_
(
'anonymous'
)}
<
/em></
span
>
$
{
"<% } %>"
}
$
{
"<% } %>"
}
<
p
class
=
"posted-details"
title
=
"${'<%- created_at %>'}"
>
$
{
'<%- created_at %>'
}
<
/p
>
<
p
class
=
"posted-details"
>
<
span
class
=
"timeago"
title
=
"${'<%= created_at %>'}"
>
$
{
'<%= created_at %>'
}
<
/span
>
<%
js_block
=
u
"""
interpolate(
endorsement.username ? "
{
user_fmt_str
}
" : "
{
anon_fmt_str
}
",
{{
'time_ago': '<span class="
timeago
" title="
' + endorsement.time + '
">' + endorsement.time + '</span>',
'user': endorsement.username
}},
true
)"""
.
format
(
##
Translators
:
time_ago
is
a
placeholder
for
a
fuzzy
,
relative
timestamp
##
like
"4 hours ago"
or
"about a month ago"
user_fmt_str
=
escapejs
(
_
(
"marked as answer %(time_ago)s by %(user)s"
)),
##
Translators
:
time_ago
is
a
placeholder
for
a
fuzzy
,
relative
timestamp
##
like
"4 hours ago"
or
"about a month ago"
anon_fmt_str
=
escapejs
(
_
(
"marked as answer %(time_ago)s"
)),
)
%>
$
{
"<% if (thread.get('thread_type') == 'question' && obj.endorsement) { %> - <%="
}
$
{
js_block
}
$
{
"%><% } %>"
}
<
/p
>
<
/header
>
<
/header
>
<
div
class
=
"response-body"
>
$
{
"<%- body %>"
}
<
/div
>
<
div
class
=
"response-body"
>
$
{
"<%- body %>"
}
<
/div
>
<
div
class
=
"discussion-flag-abuse notflagged"
data
-
role
=
"thread-flag"
role
=
"button"
aria
-
pressed
=
"false"
tabindex
=
"0"
>
<
div
class
=
"discussion-flag-abuse notflagged"
data
-
role
=
"thread-flag"
role
=
"button"
aria
-
pressed
=
"false"
tabindex
=
"0"
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment