Commit b1e1cd57 by Rocky Duan

Merge branch 'ccp0101/moderation' of github.com:dementrock/mitx into ccp0101/moderation

parents 18fcdc4b b2a048e6
......@@ -34,7 +34,7 @@ def permitted(fn):
content = None
return content
if check_permissions_by_view(request.user, fetch_content(), request.view_name):
if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name):
return fn(request, *args, **kwargs)
else:
return JsonError("unauthorized")
......
......@@ -54,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, is_thread=True) for thread in threads}
annotated_content_info = {thread['id']: get_annotated_content_info(course_id, thread, request.user, is_thread=True) for thread in threads}
context = {
'threads': threads,
......@@ -134,18 +134,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, is_thread):
def get_annotated_content_info(course_id, content, user, is_thread):
return {
'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"),
'editable': check_permissions_by_view(user, course_id, content, "update_thread" if is_thread else "update_comment"),
'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if is_thread else "create_sub_comment"),
'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if not is_thread else False,
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if is_thread else "delete_comment"),
}
def get_annotated_content_infos(thread, user, is_thread=True):
def get_annotated_content_infos(course_id, thread, user, is_thread=True):
infos = {}
def _annotate(content, is_thread=is_thread):
infos[str(content['id'])] = get_annotated_content_info(content, user, is_thread)
infos[str(content['id'])] = get_annotated_content_info(course_id, content, user, is_thread)
for child in content.get('children', []):
_annotate(child, is_thread=False)
_annotate(thread)
......@@ -155,7 +155,7 @@ 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, \
annotated_content_info = get_annotated_content_infos(course_id, thread=thread, \
user=request.user, is_thread=True)
context = {
......@@ -173,7 +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, request.user)
annotated_content_info = get_annotated_content_infos(course_id, thread, request.user)
context = {'thread': thread}
html = render_to_string('discussion/_ajax_single_thread.html', context)
......
from django.core.management.base import BaseCommand, CommandError
from django_comment_client.models import Permission, Role
from django.contrib.auth.models import User
class Command(BaseCommand):
args = 'user role course_id'
help = 'Assign a role to a user'
def handle(self, *args, **options):
role = Role.objects.get(name=args[1], course_id=args[2])
if '@' in args[0]:
user = User.objects.get(email=args[0])
else:
user = User.objects.get(username=args[0])
user.roles.add(role)
\ No newline at end of file
from django.core.management.base import BaseCommand, CommandError
from django_comment_client.models import Permission, Role
class Command(BaseCommand):
args = ''
help = 'Seed default permisssions and roles'
def handle(self, *args, **options):
moderator_role = Role.objects.get_or_create(name="Moderator", course_id="MITx/6.002x/2012_Fall")[0]
student_role = Role.objects.get_or_create(name="Student", course_id="MITx/6.002x/2012_Fall")[0]
for per in ["vote", "update_thread", "follow_thread", "unfollow_thread",
"update_comment", "create_sub_comment", "unvote" , "create_thread",
"follow_commentable", "unfollow_commentable", "create_comment", ]:
student_role.add_permission(per)
for per in ["edit_content", "delete_thread", "openclose_thread",
"endorse_comment", "delete_comment"]:
moderator_role.add_permission(per)
moderator_role.inherit_permissions(student_role)
from django.core.management.base import BaseCommand, CommandError
from django_comment_client.models import Permission, Role
from django.contrib.auth.models import User
class Command(BaseCommand):
args = 'user'
help = "Show a user's roles and permissions"
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("The number of arguments does not match. ")
try:
if '@' in args[0]:
user = User.objects.get(email=args[0])
else:
user = User.objects.get(username=args[0])
except User.DoesNotExist:
print "User %s does not exist. " % args[0]
print "Available users: "
print User.objects.all()
return
roles = user.roles.all()
print "%s has %d roles:" % (user, len(roles))
for role in roles:
print "\t%s" % role
for role in roles:
print "%s has permissions: " % role
print role.permissions.all()
......@@ -10,7 +10,9 @@ 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)),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=30)),
('course_id', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=255, blank=True)),
))
db.send_create_signal('django_comment_client', ['Role'])
......@@ -28,14 +30,6 @@ class Migration(SchemaMigration):
))
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)),
......@@ -55,9 +49,6 @@ class Migration(SchemaMigration):
# 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')
......@@ -127,12 +118,13 @@ class Migration(SchemaMigration):
'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']"})
'roles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'permissions'", 'symmetrical': 'False', 'to': "orm['django_comment_client.Role']"})
},
'django_comment_client.role': {
'Meta': {'object_name': 'Role'},
'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}),
'course_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
}
}
......
from django.db import models
from django.contrib.auth.models import User
import logging
class Role(models.Model):
name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
name = models.CharField(max_length=30, null=False, blank=False)
users = models.ManyToManyField(User, related_name="roles")
course_id = models.CharField(max_length=255, blank=True, db_index=True)
def __unicode__(self):
return self.name
return self.name + " for " + (self.course_id if self.course_id else "all courses")
@staticmethod
def register(name):
return Role.objects.get_or_create(name=name)[0]
def inherit_permissions(self, role):
if role.course_id and role.course_id != self.course_id:
logging.warning("%s cannot inheret permissions from %s due to course_id inconsistency" %
(self, role))
for per in role.permissions.all():
self.add_permission(per)
def register_permissions(self, permissions):
for p in permissions:
if not self.permissions.filter(name=p):
self.permissions.add(Permission.register(p))
def add_permission(self, permission):
self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
def inherit_permissions(self, role):
self.register_permissions(map(lambda p: p.name, role.permissions.all()))
def has_permission(self, permission):
return self.permissions.filter(name=permission).exists()
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]
......@@ -2,110 +2,98 @@ 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
from student.models import CourseEnrollment
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():
@receiver(post_save, sender=CourseEnrollment)
def assign_default_role(sender, instance, **kwargs):
if instance.user.is_staff:
role = Role.objects.get(course_id=instance.course_id, name="Moderator")
else:
role = Role.objects.get(course_id=instance.course_id, name="Student")
logging.info("assign_default_role: adding %s as %s" % (instance.user, role))
instance.user.roles.add(role)
def has_permission(user, permission, course_id=None):
# if user.permissions.filter(name=permission).exists():
# return True
for role in user.roles.filter(course_id=course_id):
if role.has_permission(permission):
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")
CONDITIONS = ['is_open', 'is_author']
def check_condition(user, condition, course_id, data):
def check_open(user, condition, course_id, data):
return not data['content']['closed']
def check_author(user, condition, course_id, data):
return data['content']['user_id'] == str(user.id)
@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)
handlers = {
'is_open' : check_open,
'is_author' : check_author,
}
return handlers[condition](user, condition, course_id, data)
def check_permissions(user, content, per):
def check_conditions_permissions(user, permissions, course_id, **kwargs):
"""
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
Note that ["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']
a list.
"""
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]
def test(user, per, operator="or"):
if isinstance(per, basestring):
if per in CONDITIONS:
return check_condition(user, per, course_id, kwargs)
return has_permission(user, per, course_id=course_id)
elif isinstance(per, list) and operator in ["and", "or"]:
results = [test(user, x, operator="and") for x in per]
if operator == "or":
return True in results
elif operator == "and":
return not False in results
return test_permission(user, permissions, operator="or")
return test(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_comment' : (['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'),
'update_thread' : ['edit_content', ['update_thread', 'is_open', 'author']],
# 'create_comment' : [["create_comment", "is_open"]],
'create_comment' : ["create_comment"],
'delete_thread' : ['delete_thread'],
'update_comment' : ['edit_content', ['update_comment', 'is_open', 'author']],
'endorse_comment' : ['endorse_comment'],
'openclose_thread' : ['openclose_thread'],
'create_sub_comment': [['create_sub_comment', 'is_open']],
'delete_comment' : ['delete_comment'],
'vote_for_comment' : [['vote', 'is_open']],
'undo_vote_for_comment': [['unvote', 'is_open']],
'vote_for_thread' : [['vote', 'is_open']],
'undo_vote_for_thread': [['unvote', 'is_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):
def check_permissions_by_view(user, course_id, content, name):
# import pdb; pdb.set_trace()
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
return check_conditions_permissions(user, p, course_id, content=content)
......@@ -89,9 +89,6 @@
${render_link("discussion-link discussion-reply discussion-reply-" + type, "Reply")}
${render_link("discussion-link discussion-edit", "Edit")}
% 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>
% 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']}" />
......
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