Commit af656fcf by Mike Chen

use permissions to check if one can edit or relp

parent 3f1534ea
......@@ -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()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment