use permissions to check if one can edit or relp
This commit is contained in:
@@ -18,62 +18,27 @@ 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 has_permission, has_permission
|
||||
from django_comment_client.permissions import check_permissions_by_view
|
||||
import functools
|
||||
|
||||
#
|
||||
|
||||
def permitted(*per):
|
||||
"""
|
||||
Accepts a list of permissions and proceed if any of the permission is valid.
|
||||
Note that @permitted("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:
|
||||
@permitted(["can_view", "can_edit"])
|
||||
|
||||
Special conditions can be used like permissions, e.g.
|
||||
@permitted(["can_vote", "open"]) # where open is True if not content['closed']
|
||||
"""
|
||||
def decorator(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
permissions = filter(lambda x: len(x), list(per))
|
||||
user = request.user
|
||||
import pdb; pdb.set_trace()
|
||||
|
||||
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:
|
||||
logging.warning("missing thread_id or comment_id")
|
||||
return None
|
||||
return content
|
||||
|
||||
def test_permission(user, permission, operator="or"):
|
||||
if isinstance(permission, basestring):
|
||||
if permission == "":
|
||||
return True
|
||||
elif permission == "author":
|
||||
return fetch_content()["user_id"] == request.user.id
|
||||
elif permission == "open":
|
||||
return not fetch_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
|
||||
|
||||
if test_permission(user, permissions, operator="or"):
|
||||
return fn(request, *args, **kwargs)
|
||||
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:
|
||||
return JsonError("unauthorized")
|
||||
content = None
|
||||
return content
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
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):
|
||||
@@ -106,7 +71,7 @@ def instructor_only(fn):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("create_thread")
|
||||
@permitted
|
||||
def create_thread(request, course_id, commentable_id):
|
||||
attributes = extract(request.POST, ['body', 'title', 'tags'])
|
||||
attributes['user_id'] = request.user.id
|
||||
@@ -131,7 +96,7 @@ def create_thread(request, course_id, commentable_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("edit_content", ["update_thread", "open", "author"])
|
||||
@permitted
|
||||
def update_thread(request, course_id, thread_id):
|
||||
attributes = extract(request.POST, ['body', 'title', 'tags'])
|
||||
response = comment_client.update_thread(thread_id, attributes)
|
||||
@@ -171,7 +136,7 @@ def _create_comment(request, course_id, _response_from_attributes):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["create_comment", "open"])
|
||||
@permitted
|
||||
def create_comment(request, course_id, thread_id):
|
||||
def _response_from_attributes(attributes):
|
||||
return comment_client.create_comment(thread_id, attributes)
|
||||
@@ -179,14 +144,14 @@ def create_comment(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("delete_thread")
|
||||
@permitted
|
||||
def delete_thread(request, course_id, thread_id):
|
||||
response = comment_client.delete_thread(thread_id)
|
||||
return JsonResponse(response)
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("update_comment", ["update_comment", "open", "author"])
|
||||
@permitted
|
||||
def update_comment(request, course_id, comment_id):
|
||||
attributes = extract(request.POST, ['body'])
|
||||
response = comment_client.update_comment(comment_id, attributes)
|
||||
@@ -205,7 +170,7 @@ def update_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("endorse_comment")
|
||||
@permitted
|
||||
def endorse_comment(request, course_id, comment_id):
|
||||
attributes = extract(request.POST, ['endorsed'])
|
||||
response = comment_client.update_comment(comment_id, attributes)
|
||||
@@ -213,7 +178,7 @@ def endorse_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("openclose_thread")
|
||||
@permitted
|
||||
def openclose_thread(request, course_id, thread_id):
|
||||
attributes = extract(request.POST, ['closed'])
|
||||
response = comment_client.update_thread(thread_id, attributes)
|
||||
@@ -221,7 +186,7 @@ def openclose_thread(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["create_sub_comment", "open"])
|
||||
@permitted
|
||||
def create_sub_comment(request, course_id, comment_id):
|
||||
def _response_from_attributes(attributes):
|
||||
return comment_client.create_sub_comment(comment_id, attributes)
|
||||
@@ -229,14 +194,14 @@ def create_sub_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("delete_comment")
|
||||
@permitted
|
||||
def delete_comment(request, course_id, comment_id):
|
||||
response = comment_client.delete_comment(comment_id)
|
||||
return JsonResponse(response)
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["vote", "open"])
|
||||
@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)
|
||||
@@ -244,7 +209,7 @@ def vote_for_comment(request, course_id, comment_id, value):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["unvote", "open"])
|
||||
@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)
|
||||
@@ -252,7 +217,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["vote", "open"])
|
||||
@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)
|
||||
@@ -260,7 +225,7 @@ def vote_for_thread(request, course_id, thread_id, value):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["unvote", "open"])
|
||||
@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)
|
||||
@@ -268,7 +233,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("follow_thread")
|
||||
@permitted
|
||||
def follow_thread(request, course_id, thread_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.subscribe_thread(user_id, thread_id)
|
||||
@@ -276,7 +241,7 @@ def follow_thread(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("follow_commentable")
|
||||
@permitted
|
||||
def follow_commentable(request, course_id, commentable_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.subscribe_commentable(user_id, commentable_id)
|
||||
@@ -284,7 +249,7 @@ def follow_commentable(request, course_id, commentable_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("follow_user")
|
||||
@permitted
|
||||
def follow_user(request, course_id, followed_user_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.follow(user_id, followed_user_id)
|
||||
@@ -292,7 +257,7 @@ def follow_user(request, course_id, followed_user_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("unfollow_thread")
|
||||
@permitted
|
||||
def unfollow_thread(request, course_id, thread_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.unsubscribe_thread(user_id, thread_id)
|
||||
@@ -300,7 +265,7 @@ def unfollow_thread(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("unfollow_commentable")
|
||||
@permitted
|
||||
def unfollow_commentable(request, course_id, commentable_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.unsubscribe_commentable(user_id, commentable_id)
|
||||
@@ -308,7 +273,7 @@ def unfollow_commentable(request, course_id, commentable_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("unfollow_user")
|
||||
@permitted
|
||||
def unfollow_user(request, course_id, followed_user_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.unfollow(user_id, followed_user_id)
|
||||
|
||||
@@ -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
|
||||
@@ -48,7 +49,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,
|
||||
@@ -127,17 +128,18 @@ def forum_form_discussion(request, course_id, discussion_id):
|
||||
return render_to_response('discussion/index.html', context)
|
||||
|
||||
|
||||
def get_annotated_content_info(content, user_id):
|
||||
def get_annotated_content_info(content, user, is_thread):
|
||||
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"),
|
||||
}
|
||||
|
||||
def get_annotated_content_infos(thread, user_id):
|
||||
def get_annotated_content_infos(thread, user):
|
||||
infos = {}
|
||||
def _annotate(content):
|
||||
infos[str(content['id'])] = get_annotated_content_info(content, user_id)
|
||||
def _annotate(content, is_thread=True):
|
||||
infos[str(content['id'])] = get_annotated_content_info(content, user, is_thread)
|
||||
for child in content.get('children', []):
|
||||
_annotate(child)
|
||||
_annotate(child, is_thread=False)
|
||||
_annotate(thread)
|
||||
return infos
|
||||
|
||||
@@ -146,7 +148,7 @@ def render_single_thread(request, 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 = {
|
||||
'thread': thread,
|
||||
@@ -162,8 +164,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)
|
||||
|
||||
|
||||
@@ -34,11 +34,76 @@ def assign_default_role(sender, instance, **kwargs):
|
||||
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",
|
||||
"update_thread", "endorse_comment", "delete_comment"])
|
||||
"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", ])
|
||||
|
||||
@@ -120,3 +120,7 @@ class JsonError(HttpResponse):
|
||||
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__
|
||||
|
||||
@@ -294,6 +294,8 @@ MIDDLEWARE_CLASSES = (
|
||||
'askbot.middleware.spaceless.SpacelessMiddleware',
|
||||
# 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
|
||||
# 'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
|
||||
'django_comment_client.utils.ViewNameMiddleware',
|
||||
)
|
||||
|
||||
############################### Pipeline #######################################
|
||||
|
||||
@@ -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)
|
||||
@@ -321,3 +322,5 @@ 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()
|
||||
|
||||
Reference in New Issue
Block a user