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
547114a8
Commit
547114a8
authored
Aug 07, 2012
by
Rocky Duan
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'ccp0101/moderation' of github.com:dementrock/mitx into ccp0101/moderation
parents
6c2f53a4
6bc69ff9
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
489 additions
and
23 deletions
+489
-23
lms/djangoapps/django_comment_client/__init__.py
+2
-0
lms/djangoapps/django_comment_client/base/urls.py
+1
-0
lms/djangoapps/django_comment_client/base/views.py
+49
-5
lms/djangoapps/django_comment_client/forum/views.py
+17
-12
lms/djangoapps/django_comment_client/migrations/0001_initial.py
+141
-0
lms/djangoapps/django_comment_client/migrations/__init__.py
+0
-0
lms/djangoapps/django_comment_client/models.py
+36
-0
lms/djangoapps/django_comment_client/permissions.py
+112
-0
lms/djangoapps/django_comment_client/tests.py
+37
-0
lms/djangoapps/django_comment_client/utils.py
+5
-1
lms/envs/common.py
+2
-0
lms/static/coffee/src/discussion/content.coffee
+58
-0
lms/static/coffee/src/discussion/utils.coffee
+1
-0
lms/templates/discussion/_search_bar.html
+5
-1
lms/templates/discussion/_thread.html
+23
-4
No files found.
lms/djangoapps/django_comment_client/__init__.py
View file @
547114a8
# call some function from permissions so that the post_save hook is imported
from
permissions
import
assign_default_role
lms/djangoapps/django_comment_client/base/urls.py
View file @
547114a8
...
...
@@ -13,6 +13,7 @@ urlpatterns = patterns('django_comment_client.base.views',
url
(
r'threads/(?P<thread_id>[\w\-]+)/unvote$'
,
'undo_vote_for_thread'
,
name
=
'undo_vote_for_thread'
),
url
(
r'threads/(?P<thread_id>[\w\-]+)/follow$'
,
'follow_thread'
,
name
=
'follow_thread'
),
url
(
r'threads/(?P<thread_id>[\w\-]+)/unfollow$'
,
'unfollow_thread'
,
name
=
'unfollow_thread'
),
url
(
r'threads/(?P<thread_id>[\w\-]+)/close$'
,
'openclose_thread'
,
name
=
'openclose_thread'
),
url
(
r'comments/(?P<comment_id>[\w\-]+)/update$'
,
'update_comment'
,
name
=
'update_comment'
),
url
(
r'comments/(?P<comment_id>[\w\-]+)/endorse$'
,
'endorse_comment'
,
name
=
'endorse_comment'
),
...
...
lms/djangoapps/django_comment_client/base/views.py
View file @
547114a8
...
...
@@ -18,6 +18,29 @@ from django.conf import settings
from
mitxmako.shortcuts
import
render_to_response
,
render_to_string
from
django_comment_client.utils
import
JsonResponse
,
JsonError
,
extract
from
django_comment_client.permissions
import
check_permissions_by_view
import
functools
def
permitted
(
fn
):
@functools.wraps
(
fn
)
def
wrapper
(
request
,
*
args
,
**
kwargs
):
def
fetch_content
():
if
"thread_id"
in
kwargs
:
content
=
comment_client
.
get_thread
(
kwargs
[
"thread_id"
])
elif
"comment_id"
in
kwargs
:
content
=
comment_client
.
get_comment
(
kwargs
[
"comment_id"
])
else
:
content
=
None
return
content
if
check_permissions_by_view
(
request
.
user
,
fetch_content
(),
request
.
view_name
):
return
fn
(
request
,
*
args
,
**
kwargs
)
else
:
return
JsonError
(
"unauthorized"
)
return
wrapper
def
thread_author_only
(
fn
):
def
verified_fn
(
request
,
*
args
,
**
kwargs
):
thread_id
=
kwargs
.
get
(
'thread_id'
,
False
)
...
...
@@ -48,6 +71,7 @@ def instructor_only(fn):
@require_POST
@login_required
@permitted
def
create_thread
(
request
,
course_id
,
commentable_id
):
attributes
=
extract
(
request
.
POST
,
[
'body'
,
'title'
,
'tags'
])
attributes
[
'user_id'
]
=
request
.
user
.
id
...
...
@@ -72,7 +96,7 @@ def create_thread(request, course_id, commentable_id):
@require_POST
@login_required
@
thread_author_only
@
permitted
def
update_thread
(
request
,
course_id
,
thread_id
):
attributes
=
extract
(
request
.
POST
,
[
'body'
,
'title'
,
'tags'
])
response
=
comment_client
.
update_thread
(
thread_id
,
attributes
)
...
...
@@ -112,6 +136,7 @@ def _create_comment(request, course_id, _response_from_attributes):
@require_POST
@login_required
@permitted
def
create_comment
(
request
,
course_id
,
thread_id
):
def
_response_from_attributes
(
attributes
):
return
comment_client
.
create_comment
(
thread_id
,
attributes
)
...
...
@@ -119,14 +144,14 @@ def create_comment(request, course_id, thread_id):
@require_POST
@login_required
@
thread_author_only
@
permitted
def
delete_thread
(
request
,
course_id
,
thread_id
):
response
=
comment_client
.
delete_thread
(
thread_id
)
return
JsonResponse
(
response
)
@require_POST
@login_required
@
comment_author_only
@
permitted
def
update_comment
(
request
,
course_id
,
comment_id
):
attributes
=
extract
(
request
.
POST
,
[
'body'
])
response
=
comment_client
.
update_comment
(
comment_id
,
attributes
)
...
...
@@ -145,7 +170,7 @@ def update_comment(request, course_id, comment_id):
@require_POST
@login_required
@
instructor_only
@
permitted
def
endorse_comment
(
request
,
course_id
,
comment_id
):
attributes
=
extract
(
request
.
POST
,
[
'endorsed'
])
response
=
comment_client
.
update_comment
(
comment_id
,
attributes
)
...
...
@@ -153,6 +178,15 @@ def endorse_comment(request, course_id, comment_id):
@require_POST
@login_required
@permitted
def
openclose_thread
(
request
,
course_id
,
thread_id
):
attributes
=
extract
(
request
.
POST
,
[
'closed'
])
response
=
comment_client
.
update_thread
(
thread_id
,
attributes
)
return
JsonResponse
(
response
)
@require_POST
@login_required
@permitted
def
create_sub_comment
(
request
,
course_id
,
comment_id
):
def
_response_from_attributes
(
attributes
):
return
comment_client
.
create_sub_comment
(
comment_id
,
attributes
)
...
...
@@ -160,13 +194,14 @@ def create_sub_comment(request, course_id, comment_id):
@require_POST
@login_required
@
comment_author_only
@
permitted
def
delete_comment
(
request
,
course_id
,
comment_id
):
response
=
comment_client
.
delete_comment
(
comment_id
)
return
JsonResponse
(
response
)
@require_POST
@login_required
@permitted
def
vote_for_comment
(
request
,
course_id
,
comment_id
,
value
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
vote_for_comment
(
comment_id
,
user_id
,
value
)
...
...
@@ -174,6 +209,7 @@ def vote_for_comment(request, course_id, comment_id, value):
@require_POST
@login_required
@permitted
def
undo_vote_for_comment
(
request
,
course_id
,
comment_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
undo_vote_for_comment
(
comment_id
,
user_id
)
...
...
@@ -181,6 +217,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
@require_POST
@login_required
@permitted
def
vote_for_thread
(
request
,
course_id
,
thread_id
,
value
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
vote_for_thread
(
thread_id
,
user_id
,
value
)
...
...
@@ -188,6 +225,7 @@ def vote_for_thread(request, course_id, thread_id, value):
@require_POST
@login_required
@permitted
def
undo_vote_for_thread
(
request
,
course_id
,
thread_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
undo_vote_for_thread
(
thread_id
,
user_id
)
...
...
@@ -195,6 +233,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
@require_POST
@login_required
@permitted
def
follow_thread
(
request
,
course_id
,
thread_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
subscribe_thread
(
user_id
,
thread_id
)
...
...
@@ -202,6 +241,7 @@ def follow_thread(request, course_id, thread_id):
@require_POST
@login_required
@permitted
def
follow_commentable
(
request
,
course_id
,
commentable_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
subscribe_commentable
(
user_id
,
commentable_id
)
...
...
@@ -209,6 +249,7 @@ def follow_commentable(request, course_id, commentable_id):
@require_POST
@login_required
@permitted
def
follow_user
(
request
,
course_id
,
followed_user_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
follow
(
user_id
,
followed_user_id
)
...
...
@@ -216,6 +257,7 @@ def follow_user(request, course_id, followed_user_id):
@require_POST
@login_required
@permitted
def
unfollow_thread
(
request
,
course_id
,
thread_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
unsubscribe_thread
(
user_id
,
thread_id
)
...
...
@@ -223,6 +265,7 @@ def unfollow_thread(request, course_id, thread_id):
@require_POST
@login_required
@permitted
def
unfollow_commentable
(
request
,
course_id
,
commentable_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
unsubscribe_commentable
(
user_id
,
commentable_id
)
...
...
@@ -230,6 +273,7 @@ def unfollow_commentable(request, course_id, commentable_id):
@require_POST
@login_required
@permitted
def
unfollow_user
(
request
,
course_id
,
followed_user_id
):
user_id
=
request
.
user
.
id
response
=
comment_client
.
unfollow
(
user_id
,
followed_user_id
)
...
...
lms/djangoapps/django_comment_client/forum/views.py
View file @
547114a8
...
...
@@ -18,6 +18,7 @@ import json
import
comment_client
import
dateutil
from
django_comment_client.permissions
import
check_permissions_by_view
THREADS_PER_PAGE
=
5
PAGES_NEARBY_DELTA
=
2
...
...
@@ -53,7 +54,7 @@ def render_discussion(request, course_id, threads, discussion_id=None, \
'forum'
:
(
lambda
:
reverse
(
'django_comment_client.forum.views.forum_form_discussion'
,
args
=
[
course_id
,
discussion_id
])),
}[
discussion_type
]()
annotated_content_info
=
{
thread
[
'id'
]:
get_annotated_content_info
(
thread
,
request
.
user
.
id
)
for
thread
in
threads
}
annotated_content_info
=
{
thread
[
'id'
]:
get_annotated_content_info
(
thread
,
request
.
user
,
is_thread
=
True
)
for
thread
in
threads
}
context
=
{
'threads'
:
threads
,
...
...
@@ -133,28 +134,32 @@ def forum_form_discussion(request, course_id, discussion_id):
return
render_to_response
(
'discussion/index.html'
,
context
)
def
get_annotated_content_info
(
content
,
user
_i
d
):
def
get_annotated_content_info
(
content
,
user
,
is_threa
d
):
return
{
'editable'
:
str
(
content
[
'user_id'
])
==
str
(
user_id
),
# TODO may relax this to instructors
'editable'
:
check_permissions_by_view
(
user
,
content
,
"update_thread"
if
is_thread
else
"update_comment"
),
'can_reply'
:
check_permissions_by_view
(
user
,
content
,
"create_comment"
if
is_thread
else
"create_sub_comment"
),
'can_endorse'
:
check_permissions_by_view
(
user
,
content
,
"endorse_comment"
)
if
not
is_thread
else
False
,
'can_delete'
:
check_permissions_by_view
(
user
,
content
,
"delete_thread"
if
is_thread
else
"delete_comment"
),
}
def
get_annotated_content_infos
(
thread
,
user
_id
):
def
get_annotated_content_infos
(
thread
,
user
,
is_thread
=
True
):
infos
=
{}
def
_annotate
(
content
):
infos
[
str
(
content
[
'id'
])]
=
get_annotated_content_info
(
content
,
user
_i
d
)
def
_annotate
(
content
,
is_thread
=
is_thread
):
infos
[
str
(
content
[
'id'
])]
=
get_annotated_content_info
(
content
,
user
,
is_threa
d
)
for
child
in
content
.
get
(
'children'
,
[]):
_annotate
(
child
)
_annotate
(
child
,
is_thread
=
False
)
_annotate
(
thread
)
return
infos
def
render_single_thread
(
request
,
course_id
,
thread_id
):
def
render_single_thread
(
request
,
discussion_id
,
course_id
,
thread_id
):
thread
=
comment_client
.
get_thread
(
thread_id
,
recursive
=
True
)
annotated_content_info
=
get_annotated_content_infos
(
thread
=
thread
,
\
user
_id
=
request
.
user
.
id
)
user
=
request
.
user
,
is_thread
=
True
)
context
=
{
'discussion_id'
:
discussion_id
,
'thread'
:
thread
,
'user_info'
:
comment_client
.
get_user_info
(
request
.
user
.
id
,
raw
=
True
),
'annotated_content_info'
:
json
.
dumps
(
annotated_content_info
),
...
...
@@ -168,8 +173,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if
request
.
is_ajax
():
thread
=
comment_client
.
get_thread
(
thread_id
,
recursive
=
True
)
annotated_content_info
=
get_annotated_content_infos
(
thread
=
thread
,
\
user_id
=
request
.
user
.
id
)
annotated_content_info
=
get_annotated_content_infos
(
thread
,
request
.
user
)
context
=
{
'thread'
:
thread
}
html
=
render_to_string
(
'discussion/_ajax_single_thread.html'
,
context
)
...
...
@@ -182,9 +186,10 @@ def single_thread(request, course_id, discussion_id, thread_id):
course
=
check_course
(
course_id
)
context
=
{
'discussion_id'
:
discussion_id
,
'csrf'
:
csrf
(
request
)[
'csrf_token'
],
'init'
:
''
,
'content'
:
render_single_thread
(
request
,
course_id
,
thread_id
),
'content'
:
render_single_thread
(
request
,
discussion_id
,
course_id
,
thread_id
),
'accordion'
:
render_accordion
(
request
,
course
,
discussion_id
),
'course'
:
course
,
}
...
...
lms/djangoapps/django_comment_client/migrations/0001_initial.py
0 → 100644
View file @
547114a8
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding model 'Role'
db
.
create_table
(
'django_comment_client_role'
,
(
(
'name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
30
,
primary_key
=
True
)),
))
db
.
send_create_signal
(
'django_comment_client'
,
[
'Role'
])
# Adding M2M table for field users on 'Role'
db
.
create_table
(
'django_comment_client_role_users'
,
(
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
primary_key
=
True
,
auto_created
=
True
)),
(
'role'
,
models
.
ForeignKey
(
orm
[
'django_comment_client.role'
],
null
=
False
)),
(
'user'
,
models
.
ForeignKey
(
orm
[
'auth.user'
],
null
=
False
))
))
db
.
create_unique
(
'django_comment_client_role_users'
,
[
'role_id'
,
'user_id'
])
# Adding model 'Permission'
db
.
create_table
(
'django_comment_client_permission'
,
(
(
'name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
30
,
primary_key
=
True
)),
))
db
.
send_create_signal
(
'django_comment_client'
,
[
'Permission'
])
# Adding M2M table for field users on 'Permission'
db
.
create_table
(
'django_comment_client_permission_users'
,
(
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
primary_key
=
True
,
auto_created
=
True
)),
(
'permission'
,
models
.
ForeignKey
(
orm
[
'django_comment_client.permission'
],
null
=
False
)),
(
'user'
,
models
.
ForeignKey
(
orm
[
'auth.user'
],
null
=
False
))
))
db
.
create_unique
(
'django_comment_client_permission_users'
,
[
'permission_id'
,
'user_id'
])
# Adding M2M table for field roles on 'Permission'
db
.
create_table
(
'django_comment_client_permission_roles'
,
(
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
primary_key
=
True
,
auto_created
=
True
)),
(
'permission'
,
models
.
ForeignKey
(
orm
[
'django_comment_client.permission'
],
null
=
False
)),
(
'role'
,
models
.
ForeignKey
(
orm
[
'django_comment_client.role'
],
null
=
False
))
))
db
.
create_unique
(
'django_comment_client_permission_roles'
,
[
'permission_id'
,
'role_id'
])
def
backwards
(
self
,
orm
):
# Deleting model 'Role'
db
.
delete_table
(
'django_comment_client_role'
)
# Removing M2M table for field users on 'Role'
db
.
delete_table
(
'django_comment_client_role_users'
)
# Deleting model 'Permission'
db
.
delete_table
(
'django_comment_client_permission'
)
# Removing M2M table for field users on 'Permission'
db
.
delete_table
(
'django_comment_client_permission_users'
)
# Removing M2M table for field roles on 'Permission'
db
.
delete_table
(
'django_comment_client_permission_roles'
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'about'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'avatar_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'n'"
,
'max_length'
:
'1'
}),
'bronze'
:
(
'django.db.models.fields.SmallIntegerField'
,
[],
{
'default'
:
'0'
}),
'consecutive_days_visit_count'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
}),
'country'
:
(
'django_countries.fields.CountryField'
,
[],
{
'max_length'
:
'2'
,
'blank'
:
'True'
}),
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'date_of_birth'
:
(
'django.db.models.fields.DateField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'display_tag_filter_strategy'
:
(
'django.db.models.fields.SmallIntegerField'
,
[],
{
'default'
:
'0'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'email_isvalid'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'email_key'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'null'
:
'True'
}),
'email_tag_filter_strategy'
:
(
'django.db.models.fields.SmallIntegerField'
,
[],
{
'default'
:
'1'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'gold'
:
(
'django.db.models.fields.SmallIntegerField'
,
[],
{
'default'
:
'0'
}),
'gravatar'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ignored_tags'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'interesting_tags'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'last_seen'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'location'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'blank'
:
'True'
}),
'new_response_count'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'questions_per_page'
:
(
'django.db.models.fields.SmallIntegerField'
,
[],
{
'default'
:
'10'
}),
'real_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'blank'
:
'True'
}),
'reputation'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{
'default'
:
'1'
}),
'seen_response_count'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
}),
'show_country'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'silver'
:
(
'django.db.models.fields.SmallIntegerField'
,
[],
{
'default'
:
'0'
}),
'status'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'w'"
,
'max_length'
:
'2'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
}),
'website'
:
(
'django.db.models.fields.URLField'
,
[],
{
'max_length'
:
'200'
,
'blank'
:
'True'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'django_comment_client.permission'
:
{
'Meta'
:
{
'object_name'
:
'Permission'
},
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'primary_key'
:
'True'
}),
'roles'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"'permissions'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['django_comment_client.Role']"
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"'permissions'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['auth.User']"
})
},
'django_comment_client.role'
:
{
'Meta'
:
{
'object_name'
:
'Role'
},
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'primary_key'
:
'True'
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"'roles'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'django_comment_client'
]
\ No newline at end of file
lms/djangoapps/django_comment_client/migrations/__init__.py
0 → 100644
View file @
547114a8
lms/djangoapps/django_comment_client/models.py
0 → 100644
View file @
547114a8
from
django.db
import
models
from
django.contrib.auth.models
import
User
class
Role
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
30
,
null
=
False
,
blank
=
False
,
primary_key
=
True
)
users
=
models
.
ManyToManyField
(
User
,
related_name
=
"roles"
)
def
__unicode__
(
self
):
return
self
.
name
@staticmethod
def
register
(
name
):
return
Role
.
objects
.
get_or_create
(
name
=
name
)[
0
]
def
register_permissions
(
self
,
permissions
):
for
p
in
permissions
:
if
not
self
.
permissions
.
filter
(
name
=
p
):
self
.
permissions
.
add
(
Permission
.
register
(
p
))
def
inherit_permissions
(
self
,
role
):
self
.
register_permissions
(
map
(
lambda
p
:
p
.
name
,
role
.
permissions
.
all
()))
class
Permission
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
30
,
null
=
False
,
blank
=
False
,
primary_key
=
True
)
users
=
models
.
ManyToManyField
(
User
,
related_name
=
"permissions"
)
roles
=
models
.
ManyToManyField
(
Role
,
related_name
=
"permissions"
)
def
__unicode__
(
self
):
return
self
.
name
@staticmethod
def
register
(
name
):
return
Permission
.
objects
.
get_or_create
(
name
=
name
)[
0
]
lms/djangoapps/django_comment_client/permissions.py
0 → 100644
View file @
547114a8
from
.models
import
Role
,
Permission
from
django.contrib.auth.models
import
User
from
django.db.models.signals
import
post_save
from
django.dispatch
import
receiver
import
logging
def
has_permission
(
user
,
p
):
if
not
Permission
.
objects
.
filter
(
name
=
p
)
.
exists
():
logging
.
warning
(
"Permission
%
s was not registered. "
%
p
)
if
Permission
.
objects
.
filter
(
users
=
user
,
name
=
p
)
.
exists
():
return
True
if
Permission
.
objects
.
filter
(
roles__in
=
user
.
roles
.
all
(),
name
=
p
)
.
exists
():
return
True
return
False
def
has_permissions
(
user
,
*
args
):
for
p
in
args
:
if
not
has_permission
(
user
,
p
):
return
False
return
True
def
add_permission
(
instance
,
p
):
permission
=
Permission
.
register
(
name
=
p
)
if
isinstance
(
instance
,
User
)
or
isinstance
(
isinstance
,
Role
):
instance
.
permissions
.
add
(
permission
)
else
:
raise
TypeError
(
"Permission can only be added to a role or user"
)
@receiver
(
post_save
,
sender
=
User
)
def
assign_default_role
(
sender
,
instance
,
**
kwargs
):
# if kwargs.get("created", True):
role
=
moderator_role
if
instance
.
is_staff
else
student_role
logging
.
info
(
"assign_default_role: adding
%
s as
%
s"
%
(
instance
,
role
))
instance
.
roles
.
add
(
role
)
def
check_permissions
(
user
,
content
,
per
):
"""
Accepts a list of permissions and proceed if any of the permission is valid.
Note that check_permissions("can_view", "can_edit") will proceed if the user has either
"can_view" or "can_edit" permission. To use AND operator in between, wrap them in
a list:
check_permissions(["can_view", "can_edit"])
Special conditions can be used like permissions, e.g.
(["can_vote", "open"]) # where open is True if not content['closed']
"""
permissions
=
filter
(
lambda
x
:
len
(
x
),
list
(
per
))
def
test_permission
(
user
,
permission
,
operator
=
"or"
):
if
isinstance
(
permission
,
basestring
):
# import pdb; pdb.set_trace()
if
permission
==
""
:
return
True
elif
permission
==
"author"
:
return
content
[
"user_id"
]
==
str
(
user
.
id
)
elif
permission
==
"open"
:
return
not
content
[
"closed"
]
return
has_permission
(
user
,
permission
)
elif
isinstance
(
permission
,
list
)
and
operator
in
[
"and"
,
"or"
]:
results
=
[
test_permission
(
user
,
x
,
operator
=
"and"
)
for
x
in
permission
]
if
operator
==
"or"
:
return
True
in
results
elif
operator
==
"and"
:
return
not
False
in
results
return
test_permission
(
user
,
permissions
,
operator
=
"or"
)
VIEW_PERMISSIONS
=
{
'update_thread'
:
(
'edit_content'
,
[
'update_thread'
,
'open'
,
'author'
]),
'create_comment'
:
([
"create_comment"
,
"open"
]),
'delete_thread'
:
(
'delete_thread'
),
'update_comment'
:
(
'edit_content'
,
[
'update_comment'
,
'open'
,
'author'
]),
'endorse_comment'
:
(
'endorse_comment'
),
'openclose_thread'
:
(
'openclose_thread'
),
'create_sub_comment'
:
([
'create_sub_comment'
,
'open'
]),
'delete_comment'
:
(
'delete_comment'
),
'vote_for_commend'
:
([
'vote'
,
'open'
]),
'undo_vote_for_comment'
:
([
'unvote'
,
'open'
]),
'vote_for_thread'
:
([
'vote'
,
'open'
]),
'undo_vote_for_thread'
:
([
'unvote'
,
'open'
]),
'follow_thread'
:
(
'follow_thread'
),
'follow_commentable'
:
(
'follow_commentable'
),
'follow_user'
:
(
'follow_user'
),
'unfollow_thread'
:
(
'unfollow_thread'
),
'unfollow_commentable'
:
(
'unfollow_commentable'
),
'unfollow_user'
:
(
'unfollow_user'
),
'create_thread'
:
(
'create_thread'
),
}
def
check_permissions_by_view
(
user
,
content
,
name
):
try
:
p
=
VIEW_PERMISSIONS
[
name
]
except
KeyError
:
logging
.
warning
(
"Permission for view named
%
s does not exist in permissions.py"
%
name
)
permissions
=
list
((
p
,
)
if
isinstance
(
p
,
basestring
)
else
p
)
return
check_permissions
(
user
,
content
,
permissions
)
moderator_role
=
Role
.
register
(
"Moderator"
)
student_role
=
Role
.
register
(
"Student"
)
moderator_role
.
register_permissions
([
"edit_content"
,
"delete_thread"
,
"openclose_thread"
,
"endorse_comment"
,
"delete_comment"
])
student_role
.
register_permissions
([
"vote"
,
"update_thread"
,
"follow_thread"
,
"unfollow_thread"
,
"update_comment"
,
"create_sub_comment"
,
"unvote"
,
"create_thread"
,
"follow_commentable"
,
"unfollow_commentable"
,
"create_comment"
,
])
moderator_role
.
inherit_permissions
(
student_role
)
\ No newline at end of file
lms/djangoapps/django_comment_client/tests.py
0 → 100644
View file @
547114a8
from
django.contrib.auth.models
import
User
from
django.utils
import
unittest
import
string
import
random
from
.permissions
import
student_role
,
moderator_role
,
add_permission
,
has_permission
from
.models
import
Role
,
Permission
class
PermissionsTestCase
(
unittest
.
TestCase
):
def
random_str
(
self
,
length
=
15
,
chars
=
string
.
ascii_uppercase
+
string
.
digits
):
return
''
.
join
(
random
.
choice
(
chars
)
for
x
in
range
(
length
))
def
setUp
(
self
):
self
.
student
=
User
.
objects
.
create
(
username
=
self
.
random_str
(),
password
=
"123456"
,
email
=
"john@yahoo.com"
)
self
.
moderator
=
User
.
objects
.
create
(
username
=
self
.
random_str
(),
password
=
"123456"
,
email
=
"staff@edx.org"
)
self
.
moderator
.
is_staff
=
True
self
.
moderator
.
save
()
def
tearDown
(
self
):
self
.
student
.
delete
()
self
.
moderator
.
delete
()
def
testDefaultRoles
(
self
):
self
.
assertTrue
(
student_role
in
self
.
student
.
roles
.
all
())
self
.
assertTrue
(
moderator_role
in
self
.
moderator
.
roles
.
all
())
def
testPermission
(
self
):
name
=
self
.
random_str
()
Permission
.
register
(
name
)
add_permission
(
moderator_role
,
name
)
self
.
assertTrue
(
has_permission
(
self
.
moderator
,
name
))
add_permission
(
self
.
student
,
name
)
self
.
assertTrue
(
has_permission
(
self
.
student
,
name
))
\ No newline at end of file
lms/djangoapps/django_comment_client/utils.py
View file @
547114a8
...
...
@@ -115,8 +115,12 @@ class JsonError(HttpResponse):
indent
=
2
,
ensure_ascii
=
False
)
super
(
JsonError
,
self
)
.
__init__
(
content
,
mimetype
=
'application/json; charset=utf8'
)
mimetype
=
'application/json; charset=utf8'
,
status
=
500
)
class
HtmlResponse
(
HttpResponse
):
def
__init__
(
self
,
html
=
''
):
super
(
HtmlResponse
,
self
)
.
__init__
(
html
,
content_type
=
'text/plain'
)
class
ViewNameMiddleware
(
object
):
def
process_view
(
self
,
request
,
view_func
,
view_args
,
view_kwargs
):
request
.
view_name
=
view_func
.
__name__
lms/envs/common.py
View file @
547114a8
...
...
@@ -305,6 +305,8 @@ MIDDLEWARE_CLASSES = (
'askbot.middleware.spaceless.SpacelessMiddleware'
,
# 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
# 'debug_toolbar.middleware.DebugToolbarMiddleware',
'django_comment_client.utils.ViewNameMiddleware'
,
)
############################### Pipeline #######################################
...
...
lms/static/coffee/src/discussion/content.coffee
View file @
547114a8
...
...
@@ -78,6 +78,7 @@ initializeFollowThread = (thread) ->
$comment
=
$
(
response
.
html
)
$content
.
children
(
".comments"
).
prepend
(
$comment
)
Discussion
.
setWmdContent
$content
,
$local
,
"reply-body"
,
""
Discussion
.
setContentInfo
response
.
content
[
'id'
],
'can_reply'
,
true
Discussion
.
setContentInfo
response
.
content
[
'id'
],
'editable'
,
true
Discussion
.
initializeContent
(
$comment
)
Discussion
.
bindContentEvents
(
$comment
)
...
...
@@ -195,6 +196,51 @@ initializeFollowThread = (thread) ->
else
$
(
content
).
removeClass
(
"endorsed"
)
handleOpenClose
=
(
elem
,
text
)
->
url
=
Discussion
.
urlFor
(
'openclose_thread'
,
id
)
closed
=
undefined
if
text
.
match
(
/Close/
)
closed
=
true
else
if
text
.
match
(
/[Oo]pen/
)
closed
=
false
else
return
console
.
log
"Unexpected text "
+
text
+
"for open/close thread."
Discussion
.
safeAjax
$elem
:
$
(
elem
)
url
:
url
type
:
"POST"
dataType
:
"json"
data
:
{
closed
:
closed
}
success
:
(
response
,
textStatus
)
=>
if
textStatus
==
"success"
if
closed
$
(
content
).
addClass
(
"closed"
)
$
(
elem
).
text
"Re-open Thread"
else
$
(
content
).
removeClass
(
"closed"
)
$
(
elem
).
text
"Close Thread"
error
:
(
response
,
textStatus
,
e
)
->
console
.
log
e
handleDelete
=
(
elem
)
->
if
$content
.
hasClass
(
"thread"
)
url
=
Discussion
.
urlFor
(
'delete_thread'
,
id
)
else
url
=
Discussion
.
urlFor
(
'delete_comment'
,
id
)
Discussion
.
safeAjax
$elem
:
$
(
elem
)
url
:
url
type
:
"POST"
dataType
:
"json"
data
:
{}
success
:
(
response
,
textStatus
)
=>
if
textStatus
==
"success"
$
(
content
).
remove
()
error
:
(
response
,
textStatus
,
e
)
->
console
.
log
e
handleHideSingleThread
=
(
elem
)
->
$threadTitle
=
$local
(
".thread-title"
)
$showComments
=
$local
(
".discussion-show-comments"
)
...
...
@@ -271,12 +317,18 @@ initializeFollowThread = (thread) ->
"click .discussion-endorse"
:
->
handleEndorse
(
this
,
$
(
this
).
is
(
":checked"
))
"click .discussion-openclose"
:
->
handleOpenClose
(
this
,
$
(
this
).
text
())
"click .discussion-edit"
:
->
if
$content
.
hasClass
(
"thread"
)
handleEditThread
(
this
)
else
handleEditComment
(
this
)
"click .discussion-delete"
:
->
handleDelete
(
this
)
initializeContent
:
(
content
)
->
unescapeHighlightTag
=
(
text
)
->
...
...
@@ -314,3 +366,9 @@ initializeFollowThread = (thread) ->
id
=
$content
.
attr
(
"_id"
)
if
not
Discussion
.
getContentInfo
id
,
'editable'
$local
(
".discussion-edit"
).
remove
()
if
not
Discussion
.
getContentInfo
id
,
'can_reply'
$local
(
".discussion-reply"
).
remove
()
if
not
Discussion
.
getContentInfo
id
,
'can_endorse'
$local
(
".discussion-endorse-control"
).
remove
()
if
not
Discussion
.
getContentInfo
id
,
'can_delete'
$local
(
".discussion-delete"
).
remove
()
lms/static/coffee/src/discussion/utils.coffee
View file @
547114a8
...
...
@@ -30,6 +30,7 @@ wmdEditors = {}
undo_vote_for_thread
:
"/courses/
#{
$$course_id
}
/discussion/threads/
#{
param
}
/unvote"
follow_thread
:
"/courses/
#{
$$course_id
}
/discussion/threads/
#{
param
}
/follow"
unfollow_thread
:
"/courses/
#{
$$course_id
}
/discussion/threads/
#{
param
}
/unfollow"
openclose_thread
:
"/courses/
#{
$$course_id
}
/discussion/threads/
#{
param
}
/close"
update_comment
:
"/courses/
#{
$$course_id
}
/discussion/comments/
#{
param
}
/update"
endorse_comment
:
"/courses/
#{
$$course_id
}
/discussion/comments/
#{
param
}
/endorse"
create_sub_comment
:
"/courses/
#{
$$course_id
}
/discussion/comments/
#{
param
}
/reply"
...
...
lms/templates/discussion/_search_bar.html
View file @
547114a8
...
...
@@ -9,6 +9,10 @@ def base_url_for_search():
%
>
<form
action=
"${base_url_for_search()}"
method=
"get"
class=
"discussion-search-form"
>
<input
class=
"search-input"
type=
"text"
value=
"${text}"
id=
"keywords"
autocomplete=
"off"
/>
% if 'tag' in query_params:
<input
class=
"search-input"
type=
"text"
value=
"[${tags}]${text}"
id=
"keywords"
autocomplete=
"off"
/>
% else:
<input
class=
"search-input"
type=
"text"
value=
"[${tags}]${text}"
id=
"keywords"
autocomplete=
"off"
/>
% endif
<div
class=
"discussion-link discussion-search-link"
href=
"javascript:void(0)"
>
Search posts
</div>
</form>
lms/templates/discussion/_thread.html
View file @
547114a8
...
...
@@ -42,6 +42,7 @@
<div
class=
"discussion-right-wrapper clearfix"
>
${render_title(content, type, **kwargs)}
<div
class=
"discussion-content-view"
>
<a
name=
"${content['id']}"
></a>
% if content.get('highlighted_body', None):
<div
class=
"content-body ${type}-body"
id=
"content-body-${content['id']}"
>
${content['highlighted_body'] | h}
</div>
% else:
...
...
@@ -87,14 +88,32 @@
${render_info(content)}
${render_link("discussion-link discussion-reply discussion-reply-" + type, "Reply")}
${render_link("discussion-link discussion-edit", "Edit")}
% if type == "comment" and request.user.is_staff:
% if content['endorsed']:
<input
type=
"checkbox"
checked=
"checked"
class=
"discussion-link discussion-endorse"
id=
"discussion-endorse-${content['id']}"
>
% if type == "thread":
<a
class=
"discussion-link discussion-permanent-link"
href=
"${reverse('django_comment_client.forum.views.single_thread', kwargs={'discussion_id':discussion_id, 'thread_id':content['id'], 'course_id':course_id})}"
>
Permanent Link
</a>
% else:
<a
class=
"discussion-link discussion-permanent-link"
href=
"${reverse('django_comment_client.forum.views.single_thread', kwargs={'discussion_id':discussion_id, 'thread_id':content['thread_id'], 'course_id':course_id})}#${content['id']}"
>
Permanent Link
</a>
% endif
<span
class=
"discussion-endorse-control"
>
% if content.get('endorsed', False):
<input
type=
"checkbox"
checked=
"checked"
class=
"discussion-link discussion-endorse"
id=
"discussion-endorse-${content['id']}"
/>
% else:
<input
type=
"checkbox"
class=
"discussion-link discussion-endorse"
id=
"discussion-endorse-${content['id']}"
>
<input
type=
"checkbox"
class=
"discussion-link discussion-endorse"
id=
"discussion-endorse-${content['id']}"
/
>
% endif
<label
class=
"discussion-link"
for=
"discussion-endorse-${content['id']}"
>
Endorsed
</label>
</span>
% if type == "thread" and request.user.is_staff:
% if content['closed']:
<a
class=
"discussion-openclose"
id=
"discussion-openclose-${content['id']}"
href=
"javascript:void(0);"
>
Re-open thread
</a>
% else:
<a
class=
"discussion-openclose"
id=
"discussion-openclose-${content['id']}"
href=
"javascript:void(0);"
>
Close thread
</a>
% endif
% endif
${render_link("discussion-link discussion-delete", "Delete")}
</div>
</
%
def>
...
...
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