Merge branch 'ccp0101/moderation' into lol
This commit is contained in:
@@ -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,
|
||||
@@ -148,18 +148,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)
|
||||
@@ -169,7 +169,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 = {
|
||||
@@ -187,7 +187,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)
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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)
|
||||
@@ -0,0 +1,22 @@
|
||||
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)
|
||||
@@ -0,0 +1,31 @@
|
||||
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']"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
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
|
||||
|
||||
@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))
|
||||
return self.name + " for " + (self.course_id if self.course_id else "all courses")
|
||||
|
||||
def inherit_permissions(self, role):
|
||||
self.register_permissions(map(lambda p: p.name, role.permissions.all()))
|
||||
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 add_permission(self, permission):
|
||||
self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
|
||||
|
||||
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():
|
||||
return True
|
||||
|
||||
@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)
|
||||
|
||||
handlers = {
|
||||
'is_open' : check_open,
|
||||
'is_author' : check_author,
|
||||
}
|
||||
|
||||
return handlers[condition](user, condition, course_id, data)
|
||||
|
||||
|
||||
@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):
|
||||
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)
|
||||
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']}" />
|
||||
|
||||
Reference in New Issue
Block a user