added permission framework.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# call some function from permissions so that the post_save hook is imported
|
||||
from permissions import assign_default_role
|
||||
|
||||
@@ -13,6 +13,7 @@ urlpatterns = patterns('django_comment_client.base.views',
|
||||
url(r'threads/(?P<thread_id>[\w\-]+)/unvote$', 'undo_vote_for_thread', name='undo_vote_for_thread'),
|
||||
url(r'threads/(?P<thread_id>[\w\-]+)/follow$', 'follow_thread', name='follow_thread'),
|
||||
url(r'threads/(?P<thread_id>[\w\-]+)/unfollow$', 'unfollow_thread', name='unfollow_thread'),
|
||||
url(r'threads/(?P<thread_id>[\w\-]+)/close$', 'openclose_thread', name='openclose_thread'),
|
||||
|
||||
url(r'comments/(?P<comment_id>[\w\-]+)/update$', 'update_comment', name='update_comment'),
|
||||
url(r'comments/(?P<comment_id>[\w\-]+)/endorse$', 'endorse_comment', name='endorse_comment'),
|
||||
|
||||
@@ -18,6 +18,64 @@ 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
|
||||
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)
|
||||
else:
|
||||
return JsonError("unauthorized")
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def thread_author_only(fn):
|
||||
def verified_fn(request, *args, **kwargs):
|
||||
thread_id = kwargs.get('thread_id', False)
|
||||
@@ -48,6 +106,7 @@ def instructor_only(fn):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("create_thread")
|
||||
def create_thread(request, course_id, commentable_id):
|
||||
attributes = extract(request.POST, ['body', 'title', 'tags'])
|
||||
attributes['user_id'] = request.user.id
|
||||
@@ -72,7 +131,7 @@ def create_thread(request, course_id, commentable_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@thread_author_only
|
||||
@permitted("edit_content", ["update_thread", "open", "author"])
|
||||
def update_thread(request, course_id, thread_id):
|
||||
attributes = extract(request.POST, ['body', 'title', 'tags'])
|
||||
response = comment_client.update_thread(thread_id, attributes)
|
||||
@@ -112,6 +171,7 @@ def _create_comment(request, course_id, _response_from_attributes):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["create_comment", "open"])
|
||||
def create_comment(request, course_id, thread_id):
|
||||
def _response_from_attributes(attributes):
|
||||
return comment_client.create_comment(thread_id, attributes)
|
||||
@@ -119,14 +179,14 @@ def create_comment(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@thread_author_only
|
||||
@permitted("delete_thread")
|
||||
def delete_thread(request, course_id, thread_id):
|
||||
response = comment_client.delete_thread(thread_id)
|
||||
return JsonResponse(response)
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@comment_author_only
|
||||
@permitted("update_comment", ["update_comment", "open", "author"])
|
||||
def update_comment(request, course_id, comment_id):
|
||||
attributes = extract(request.POST, ['body'])
|
||||
response = comment_client.update_comment(comment_id, attributes)
|
||||
@@ -145,7 +205,7 @@ def update_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@instructor_only
|
||||
@permitted("endorse_comment")
|
||||
def endorse_comment(request, course_id, comment_id):
|
||||
attributes = extract(request.POST, ['endorsed'])
|
||||
response = comment_client.update_comment(comment_id, attributes)
|
||||
@@ -153,6 +213,15 @@ def endorse_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("openclose_thread")
|
||||
def openclose_thread(request, course_id, thread_id):
|
||||
attributes = extract(request.POST, ['closed'])
|
||||
response = comment_client.update_thread(thread_id, attributes)
|
||||
return JsonResponse(response)
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["create_sub_comment", "open"])
|
||||
def create_sub_comment(request, course_id, comment_id):
|
||||
def _response_from_attributes(attributes):
|
||||
return comment_client.create_sub_comment(comment_id, attributes)
|
||||
@@ -160,13 +229,14 @@ def create_sub_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@comment_author_only
|
||||
@permitted("delete_comment")
|
||||
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"])
|
||||
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)
|
||||
@@ -174,6 +244,7 @@ def vote_for_comment(request, course_id, comment_id, value):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["unvote", "open"])
|
||||
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)
|
||||
@@ -181,6 +252,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["vote", "open"])
|
||||
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)
|
||||
@@ -188,6 +260,7 @@ def vote_for_thread(request, course_id, thread_id, value):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted(["unvote", "open"])
|
||||
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)
|
||||
@@ -195,6 +268,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("follow_thread")
|
||||
def follow_thread(request, course_id, thread_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.subscribe_thread(user_id, thread_id)
|
||||
@@ -202,6 +276,7 @@ def follow_thread(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("follow_commentable")
|
||||
def follow_commentable(request, course_id, commentable_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.subscribe_commentable(user_id, commentable_id)
|
||||
@@ -209,6 +284,7 @@ def follow_commentable(request, course_id, commentable_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("follow_user")
|
||||
def follow_user(request, course_id, followed_user_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.follow(user_id, followed_user_id)
|
||||
@@ -216,6 +292,7 @@ def follow_user(request, course_id, followed_user_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("unfollow_thread")
|
||||
def unfollow_thread(request, course_id, thread_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.unsubscribe_thread(user_id, thread_id)
|
||||
@@ -223,6 +300,7 @@ def unfollow_thread(request, course_id, thread_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("unfollow_commentable")
|
||||
def unfollow_commentable(request, course_id, commentable_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.unsubscribe_commentable(user_id, commentable_id)
|
||||
@@ -230,6 +308,7 @@ def unfollow_commentable(request, course_id, commentable_id):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@permitted("unfollow_user")
|
||||
def unfollow_user(request, course_id, followed_user_id):
|
||||
user_id = request.user.id
|
||||
response = comment_client.unfollow(user_id, followed_user_id)
|
||||
|
||||
140
lms/djangoapps/django_comment_client/migrations/0001_initial.py
Normal file
140
lms/djangoapps/django_comment_client/migrations/0001_initial.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
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)),
|
||||
))
|
||||
db.send_create_signal('django_comment_client', ['Role'])
|
||||
|
||||
# Adding M2M table for field users on 'Role'
|
||||
db.create_table('django_comment_client_role_users', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('role', models.ForeignKey(orm['django_comment_client.role'], null=False)),
|
||||
('user', models.ForeignKey(orm['auth.user'], null=False))
|
||||
))
|
||||
db.create_unique('django_comment_client_role_users', ['role_id', 'user_id'])
|
||||
|
||||
# Adding model 'Permission'
|
||||
db.create_table('django_comment_client_permission', (
|
||||
('name', self.gf('django.db.models.fields.CharField')(max_length=30, primary_key=True)),
|
||||
))
|
||||
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)),
|
||||
('permission', models.ForeignKey(orm['django_comment_client.permission'], null=False)),
|
||||
('role', models.ForeignKey(orm['django_comment_client.role'], null=False))
|
||||
))
|
||||
db.create_unique('django_comment_client_permission_roles', ['permission_id', 'role_id'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'Role'
|
||||
db.delete_table('django_comment_client_role')
|
||||
|
||||
# Removing M2M table for field users on 'Role'
|
||||
db.delete_table('django_comment_client_role_users')
|
||||
|
||||
# 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')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
|
||||
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
|
||||
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
|
||||
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'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']"})
|
||||
},
|
||||
'django_comment_client.role': {
|
||||
'Meta': {'object_name': 'Role'},
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['django_comment_client']
|
||||
36
lms/djangoapps/django_comment_client/models.py
Normal file
36
lms/djangoapps/django_comment_client/models.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class Role(models.Model):
|
||||
name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
|
||||
users = models.ManyToManyField(User, related_name="roles")
|
||||
|
||||
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))
|
||||
|
||||
def inherit_permissions(self, role):
|
||||
self.register_permissions(map(lambda p: p.name, role.permissions.all()))
|
||||
|
||||
|
||||
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]
|
||||
|
||||
46
lms/djangoapps/django_comment_client/permissions.py
Normal file
46
lms/djangoapps/django_comment_client/permissions.py
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
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
|
||||
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")
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
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"])
|
||||
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)
|
||||
36
lms/djangoapps/django_comment_client/tests.py
Normal file
36
lms/djangoapps/django_comment_client/tests.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import unittest
|
||||
import string
|
||||
import random
|
||||
from .permissions import student_role, moderator_role, add_permission, has_permission
|
||||
from .models import Role, Permission
|
||||
|
||||
|
||||
class PermissionsTestCase(unittest.TestCase):
|
||||
def random_str(self, length=15, chars=string.ascii_uppercase + string.digits):
|
||||
return ''.join(random.choice(chars) for x in range(length))
|
||||
|
||||
def setUp(self):
|
||||
self.student = User.objects.create(username=self.random_str(),
|
||||
password="123456", email="john@yahoo.com")
|
||||
self.moderator = User.objects.create(username=self.random_str(),
|
||||
password="123456", email="staff@edx.org")
|
||||
self.moderator.is_staff = True
|
||||
self.moderator.save()
|
||||
|
||||
def tearDown(self):
|
||||
self.student.delete()
|
||||
self.moderator.delete()
|
||||
|
||||
def testDefaultRoles(self):
|
||||
self.assertTrue(student_role in self.student.roles.all())
|
||||
self.assertTrue(moderator_role in self.moderator.roles.all())
|
||||
|
||||
def testPermission(self):
|
||||
name = self.random_str()
|
||||
Permission.register(name)
|
||||
add_permission(moderator_role, name)
|
||||
self.assertTrue(has_permission(self.moderator, name))
|
||||
|
||||
add_permission(self.student, name)
|
||||
self.assertTrue(has_permission(self.student, name))
|
||||
@@ -115,7 +115,7 @@ class JsonError(HttpResponse):
|
||||
indent=2,
|
||||
ensure_ascii=False)
|
||||
super(JsonError, self).__init__(content,
|
||||
mimetype='application/json; charset=utf8')
|
||||
mimetype='application/json; charset=utf8', status=500)
|
||||
|
||||
class HtmlResponse(HttpResponse):
|
||||
def __init__(self, html=''):
|
||||
|
||||
@@ -195,6 +195,33 @@ initializeFollowThread = (thread) ->
|
||||
else
|
||||
$(content).removeClass("endorsed")
|
||||
|
||||
handleOpenClose = (elem, text) ->
|
||||
url = Discussion.urlFor('openclose_thread', id)
|
||||
closed = undefined
|
||||
if text.match(/Close/)
|
||||
closed = true
|
||||
else if text.match(/[Oo]pen/)
|
||||
closed = false
|
||||
else
|
||||
return console.log "Unexpected text " + text + "for open/close thread."
|
||||
|
||||
Discussion.safeAjax
|
||||
$elem: $(elem)
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: "json"
|
||||
data: {closed: closed}
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == "success"
|
||||
if closed
|
||||
$(content).addClass("closed")
|
||||
$(elem).text "Re-open Thread"
|
||||
else
|
||||
$(content).removeClass("closed")
|
||||
$(elem).text "Close Thread"
|
||||
error: (response, textStatus, e) ->
|
||||
console.log e
|
||||
|
||||
handleHideSingleThread = (elem) ->
|
||||
$threadTitle = $local(".thread-title")
|
||||
$showComments = $local(".discussion-show-comments")
|
||||
@@ -271,6 +298,9 @@ initializeFollowThread = (thread) ->
|
||||
"click .discussion-endorse": ->
|
||||
handleEndorse(this, $(this).is(":checked"))
|
||||
|
||||
"click .discussion-openclose": ->
|
||||
handleOpenClose(this, $(this).text())
|
||||
|
||||
"click .discussion-edit": ->
|
||||
if $content.hasClass("thread")
|
||||
handleEditThread(this)
|
||||
|
||||
@@ -29,6 +29,7 @@ wmdEditors = {}
|
||||
undo_vote_for_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unvote"
|
||||
follow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/follow"
|
||||
unfollow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unfollow"
|
||||
openclose_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/close"
|
||||
update_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/update"
|
||||
endorse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/endorse"
|
||||
create_sub_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/reply"
|
||||
|
||||
@@ -86,6 +86,13 @@
|
||||
% endif
|
||||
<label class="discussion-link" for="discussion-endorse-${content['id']}">Endorsed</label>
|
||||
% endif
|
||||
% if type == "thread" and request.user.is_staff:
|
||||
% if content['closed']:
|
||||
<a class="discussion-openclose" id="discussion-openclose-${content['id']}" href="javascript:void(0);">Re-open thread</a>
|
||||
% else:
|
||||
<a class="discussion-openclose" id="discussion-openclose-${content['id']}" href="javascript:void(0);">Close thread</a>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user