diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py
index d921198b16..c21fa6a3f3 100644
--- a/lms/djangoapps/django_comment_client/base/views.py
+++ b/lms/djangoapps/django_comment_client/base/views.py
@@ -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")
diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py
index 565f683a60..b8f767e8bd 100644
--- a/lms/djangoapps/django_comment_client/forum/views.py
+++ b/lms/djangoapps/django_comment_client/forum/views.py
@@ -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)
diff --git a/lms/djangoapps/django_comment_client/management/__init__.py b/lms/djangoapps/django_comment_client/management/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lms/djangoapps/django_comment_client/management/commands/__init__.py b/lms/djangoapps/django_comment_client/management/commands/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lms/djangoapps/django_comment_client/management/commands/assign_role.py b/lms/djangoapps/django_comment_client/management/commands/assign_role.py
new file mode 100644
index 0000000000..82daa34622
--- /dev/null
+++ b/lms/djangoapps/django_comment_client/management/commands/assign_role.py
@@ -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)
\ No newline at end of file
diff --git a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py
new file mode 100644
index 0000000000..fd09e97d27
--- /dev/null
+++ b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py
@@ -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)
diff --git a/lms/djangoapps/django_comment_client/management/commands/show_permissions.py b/lms/djangoapps/django_comment_client/management/commands/show_permissions.py
new file mode 100644
index 0000000000..3f99bbaca0
--- /dev/null
+++ b/lms/djangoapps/django_comment_client/management/commands/show_permissions.py
@@ -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()
diff --git a/lms/djangoapps/django_comment_client/migrations/0001_initial.py b/lms/djangoapps/django_comment_client/migrations/0001_initial.py
index 826cbdae35..4993984d74 100644
--- a/lms/djangoapps/django_comment_client/migrations/0001_initial.py
+++ b/lms/djangoapps/django_comment_client/migrations/0001_initial.py
@@ -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']"})
}
}
diff --git a/lms/djangoapps/django_comment_client/models.py b/lms/djangoapps/django_comment_client/models.py
index 22794677fa..06bf8c0d6b 100644
--- a/lms/djangoapps/django_comment_client/models.py
+++ b/lms/djangoapps/django_comment_client/models.py
@@ -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]
-
diff --git a/lms/djangoapps/django_comment_client/permissions.py b/lms/djangoapps/django_comment_client/permissions.py
index 9beb2b8dae..eae67a8361 100644
--- a/lms/djangoapps/django_comment_client/permissions.py
+++ b/lms/djangoapps/django_comment_client/permissions.py
@@ -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)
\ No newline at end of file
+ return check_conditions_permissions(user, p, course_id, content=content)
diff --git a/lms/templates/discussion/_thread.html b/lms/templates/discussion/_thread.html
index eb2d4f3468..c22e41eaad 100644
--- a/lms/templates/discussion/_thread.html
+++ b/lms/templates/discussion/_thread.html
@@ -89,9 +89,6 @@
${render_link("discussion-link discussion-reply discussion-reply-" + type, "Reply")}
${render_link("discussion-link discussion-edit", "Edit")}
- % if type == 'thread':
- Permanent Link
- % endif
% if content.get('endorsed', False):