Commit b7d33618 by Rocky Duan

Merge branch 'mergedtom' into refactor

Conflicts:
	lms/djangoapps/django_comment_client/forum/views.py
parents de8c828f 2848c1fe
......@@ -121,6 +121,7 @@ MIDDLEWARE_CLASSES = (
)
############################ SIGNAL HANDLERS ################################
# This is imported to register the exception signal handling that logs exceptions
import monitoring.exceptions # noqa
############################ DJANGO_BUILTINS ################################
......
import logging
from django.conf import settings
from django.http import HttpResponseServerError
log = logging.getLogger("mitx")
class ExceptionLoggingMiddleware(object):
"""Just here to log unchecked exceptions that go all the way up the Django
stack"""
if not settings.TEMPLATE_DEBUG:
def process_exception(self, request, exception):
log.exception(exception)
return HttpResponseServerError("Server Error - Please try again later.")
......@@ -142,7 +142,7 @@ def get_module(user, request, location, student_module_cache, position=None):
shared_module = None
instance_state = instance_module.state if instance_module is not None else {}
instance_state = instance_module.state if instance_module is not None else '{}'
shared_state = shared_module.state if shared_module is not None else None
# TODO (vshnayder): fix hardcoded urls (use reverse)
......
......@@ -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,
......@@ -153,18 +153,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)
......@@ -175,7 +175,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id):
thread = cc.Thread.find(thread_id).retrieve_with_comments()
#comment_client.get_thread(thread_id, recursive=True)
annotated_content_info = get_annotated_content_infos(thread=dict(thread), \
annotated_content_info = get_annotated_content_infos(course_id, thread=dict(thread), \
user=request.user, is_thread=True)
context = {
......@@ -193,7 +193,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if request.is_ajax():
thread = cc.Thread.find(thread_id).retrieve_with_comments()#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': dict(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():
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)
@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)
......@@ -174,6 +174,9 @@ MODULESTORE = {
}
}
############################ SIGNAL HANDLERS ################################
# This is imported to register the exception signal handling that logs exceptions
import monitoring.exceptions # noqa
############################### DJANGO BUILT-INS ###############################
# Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
......@@ -286,7 +289,6 @@ TEMPLATE_LOADERS = (
)
MIDDLEWARE_CLASSES = (
'util.middleware.ExceptionLoggingMiddleware',
'django_comment_client.middleware.AjaxExceptionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
......
/Users/dementrock/coding/cs_comments_client_python/comment_client
\ No newline at end of file
import requests
import json
SERVICE_HOST = 'http://localhost:4567'
PREFIX = SERVICE_HOST + '/api/v1'
class CommentClientError(Exception):
def __init__(self, msg):
self.message = msg
class CommentClientUnknownError(CommentClientError):
pass
def delete_threads(commentable_id, *args, **kwargs):
return _perform_request('delete', _url_for_commentable_threads(commentable_id), *args, **kwargs)
def get_threads(commentable_id, recursive=False, query_params={}, *args, **kwargs):
default_params = {'page': 1, 'per_page': 20, 'recursive': recursive}
attributes = dict(default_params.items() + query_params.items())
response = _perform_request('get', _url_for_threads(commentable_id), \
attributes, *args, **kwargs)
return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1)
def search_threads(course_id, recursive=False, query_params={}, *args, **kwargs):
default_params = {'page': 1, 'per_page': 20, 'course_id': course_id, 'recursive': recursive}
attributes = dict(default_params.items() + query_params.items())
response = _perform_request('get', _url_for_search_threads(), \
attributes, *args, **kwargs)
return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1)
def search_similar_threads(course_id, recursive=False, query_params={}, *args, **kwargs):
default_params = {'course_id': course_id, 'recursive': recursive}
attributes = dict(default_params.items() + query_params.items())
return _perform_request('get', _url_for_search_similar_threads(), attributes, *args, **kwargs)
def search_recent_active_threads(course_id, recursive=False, query_params={}, *args, **kwargs):
default_params = {'course_id': course_id, 'recursive': recursive}
attributes = dict(default_params.items() + query_params.items())
return _perform_request('get', _url_for_search_recent_active_threads(), attributes, *args, **kwargs)
def search_trending_tags(course_id, query_params={}, *args, **kwargs):
default_params = {'course_id': course_id}
attributes = dict(default_params.items() + query_params.items())
return _perform_request('get', _url_for_search_trending_tags(), attributes, *args, **kwargs)
def create_user(attributes, *args, **kwargs):
return _perform_request('post', _url_for_users(), attributes, *args, **kwargs)
def update_user(user_id, attributes, *args, **kwargs):
return _perform_request('put', _url_for_user(user_id), attributes, *args, **kwargs)
def get_threads_tags(*args, **kwargs):
return _perform_request('get', _url_for_threads_tags(), {}, *args, **kwargs)
def tags_autocomplete(value, *args, **kwargs):
return _perform_request('get', _url_for_threads_tags_autocomplete(), {'value': value}, *args, **kwargs)
def create_thread(commentable_id, attributes, *args, **kwargs):
return _perform_request('post', _url_for_threads(commentable_id), attributes, *args, **kwargs)
def get_thread(thread_id, recursive=False, *args, **kwargs):
return _perform_request('get', _url_for_thread(thread_id), {'recursive': recursive}, *args, **kwargs)
def update_thread(thread_id, attributes, *args, **kwargs):
return _perform_request('put', _url_for_thread(thread_id), attributes, *args, **kwargs)
def create_comment(thread_id, attributes, *args, **kwargs):
return _perform_request('post', _url_for_thread_comments(thread_id), attributes, *args, **kwargs)
def delete_thread(thread_id, *args, **kwargs):
return _perform_request('delete', _url_for_thread(thread_id), *args, **kwargs)
def get_comment(comment_id, recursive=False, *args, **kwargs):
return _perform_request('get', _url_for_comment(comment_id), {'recursive': recursive}, *args, **kwargs)
def update_comment(comment_id, attributes, *args, **kwargs):
return _perform_request('put', _url_for_comment(comment_id), attributes, *args, **kwargs)
def create_sub_comment(comment_id, attributes, *args, **kwargs):
return _perform_request('post', _url_for_comment(comment_id), attributes, *args, **kwargs)
def delete_comment(comment_id, *args, **kwargs):
return _perform_request('delete', _url_for_comment(comment_id), *args, **kwargs)
def vote_for_comment(comment_id, user_id, value, *args, **kwargs):
return _perform_request('put', _url_for_vote_comment(comment_id), {'user_id': user_id, 'value': value}, *args, **kwargs)
def undo_vote_for_comment(comment_id, user_id, *args, **kwargs):
return _perform_request('delete', _url_for_vote_comment(comment_id), {'user_id': user_id}, *args, **kwargs)
def vote_for_thread(thread_id, user_id, value, *args, **kwargs):
return _perform_request('put', _url_for_vote_thread(thread_id), {'user_id': user_id, 'value': value}, *args, **kwargs)
def undo_vote_for_thread(thread_id, user_id, *args, **kwargs):
return _perform_request('delete', _url_for_vote_thread(thread_id), {'user_id': user_id}, *args, **kwargs)
def get_notifications(user_id, *args, **kwargs):
return _perform_request('get', _url_for_notifications(user_id), *args, **kwargs)
def get_user_info(user_id, complete=True, *args, **kwargs):
return _perform_request('get', _url_for_user(user_id), {'complete': complete}, *args, **kwargs)
def subscribe(user_id, subscription_detail, *args, **kwargs):
return _perform_request('post', _url_for_subscription(user_id), subscription_detail, *args, **kwargs)
def subscribe_user(user_id, followed_user_id, *args, **kwargs):
return subscribe(user_id, {'source_type': 'user', 'source_id': followed_user_id})
follow = subscribe_user
def subscribe_thread(user_id, thread_id, *args, **kwargs):
return subscribe(user_id, {'source_type': 'thread', 'source_id': thread_id})
def subscribe_commentable(user_id, commentable_id, *args, **kwargs):
return subscribe(user_id, {'source_type': 'other', 'source_id': commentable_id})
def unsubscribe(user_id, subscription_detail, *args, **kwargs):
return _perform_request('delete', _url_for_subscription(user_id), subscription_detail, *args, **kwargs)
def unsubscribe_user(user_id, followed_user_id, *args, **kwargs):
return unsubscribe(user_id, {'source_type': 'user', 'source_id': followed_user_id})
unfollow = unsubscribe_user
def unsubscribe_thread(user_id, thread_id, *args, **kwargs):
return unsubscribe(user_id, {'source_type': 'thread', 'source_id': thread_id})
def unsubscribe_commentable(user_id, commentable_id, *args, **kwargs):
return unsubscribe(user_id, {'source_type': 'other', 'source_id': commentable_id})
def _perform_request(method, url, data_or_params=None, *args, **kwargs):
if method in ['post', 'put', 'patch']:
response = requests.request(method, url, data=data_or_params)
else:
response = requests.request(method, url, params=data_or_params)
if 200 < response.status_code < 500:
raise CommentClientError(response.text)
elif response.status_code == 500:
raise CommentClientUnknownError(response.text)
else:
if kwargs.get("raw", False):
return response.text
else:
return json.loads(response.text)
def _url_for_threads(commentable_id):
return "{prefix}/{commentable_id}/threads".format(prefix=PREFIX, commentable_id=commentable_id)
def _url_for_thread(thread_id):
return "{prefix}/threads/{thread_id}".format(prefix=PREFIX, thread_id=thread_id)
def _url_for_thread_comments(thread_id):
return "{prefix}/threads/{thread_id}/comments".format(prefix=PREFIX, thread_id=thread_id)
def _url_for_comment(comment_id):
return "{prefix}/comments/{comment_id}".format(prefix=PREFIX, comment_id=comment_id)
def _url_for_vote_comment(comment_id):
return "{prefix}/comments/{comment_id}/votes".format(prefix=PREFIX, comment_id=comment_id)
def _url_for_vote_thread(thread_id):
return "{prefix}/threads/{thread_id}/votes".format(prefix=PREFIX, thread_id=thread_id)
def _url_for_notifications(user_id):
return "{prefix}/users/{user_id}/notifications".format(prefix=PREFIX, user_id=user_id)
def _url_for_subscription(user_id):
return "{prefix}/users/{user_id}/subscriptions".format(prefix=PREFIX, user_id=user_id)
def _url_for_user(user_id):
return "{prefix}/users/{user_id}".format(prefix=PREFIX, user_id=user_id)
def _url_for_search_threads():
return "{prefix}/search/threads".format(prefix=PREFIX)
def _url_for_search_similar_threads():
return "{prefix}/search/threads/more_like_this".format(prefix=PREFIX)
def _url_for_search_recent_active_threads():
return "{prefix}/search/threads/recent_active".format(prefix=PREFIX)
def _url_for_search_trending_tags():
return "{prefix}/search/tags/trending".format(prefix=PREFIX)
def _url_for_threads_tags():
return "{prefix}/threads/tags".format(prefix=PREFIX)
def _url_for_threads_tags_autocomplete():
return "{prefix}/threads/tags/autocomplete".format(prefix=PREFIX)
def _url_for_users():
return "{prefix}/users".format(prefix=PREFIX)
......@@ -183,6 +183,8 @@ initializeFollowThread = (thread) ->
handleEndorse = (elem, endorsed) ->
url = Discussion.urlFor('endorse_comment', id)
console.log endorsed
console.log url
Discussion.safeAjax
$elem: $(elem)
url: url
......@@ -196,6 +198,9 @@ initializeFollowThread = (thread) ->
else
$(content).removeClass("endorsed")
$(elem).unbind('click').click ->
handleEndorse(elem, !endorsed)
handleOpenClose = (elem, text) ->
url = Discussion.urlFor('openclose_thread', id)
closed = undefined
......@@ -314,19 +319,19 @@ initializeFollowThread = (thread) ->
else
handleVote($elem, "down")
"click .discussion-endorse": ->
handleEndorse(this, $(this).is(":checked"))
"click .admin-endorse": ->
handleEndorse(this, true)#, $(this).is(":checked"))
"click .discussion-openclose": ->
handleOpenClose(this, $(this).text())
"click .discussion-edit": ->
"click .admin-edit": ->
if $content.hasClass("thread")
handleEditThread(this)
else
handleEditComment(this)
"click .discussion-delete": ->
"click .admin-delete": ->
handleDelete(this)
initializeContent: (content) ->
......
......@@ -36,7 +36,7 @@ initializeFollowDiscussion = (discussion) ->
title = $local(".new-post-title").val()
body = Discussion.getWmdContent $discussion, $local, "new-post-body"
tags = $local(".new-post-tags").val()
url = Discussion.urlFor('create_thread', $local(".new-post-form").attr("_id"))
url = Discussion.urlFor('create_thread', id)
Discussion.safeAjax
$elem: $(elem)
url: url
......@@ -95,33 +95,33 @@ initializeFollowDiscussion = (discussion) ->
$wrapper.hide()
$title.attr("prev-text", text)
handleNewPost = (elem) ->
newPostForm = $local(".new-post-form")
if newPostForm.length
newPostForm.show()
$(elem).hide()
else
view = { discussion_id: id }
$newPostButton = $local(".discussion-new-post")
$newPostButton.after Mustache.render Discussion.newPostTemplate, view
newPostBody = $discussion.find(".new-post-body")
if newPostBody.length
Discussion.makeWmdEditor $discussion, $local, "new-post-body"
initializeNewPost = (elem) ->
#newPostForm = $local(".new-post-form")
#view = { discussion_id: id }
#$newPostButton = $local(".discussion-new-post")
#$newPostButton.after Mustache.render Discussion.newPostTemplate, view
newPostBody = $discussion.find(".new-post-body")
if newPostBody.length
Discussion.makeWmdEditor $discussion, $local, "new-post-body"
$input = Discussion.getWmdInput($discussion, $local, "new-post-body")
$input.attr("placeholder", "post a new topic...").bind 'focus', (e) ->
$local(".new-post-form").removeClass('collapsed')
$local(".new-post-tags").tagsInput Discussion.tagsInputOptions()
$local(".new-post-tags").tagsInput Discussion.tagsInputOptions()
$local(".new-post-title").blur ->
handleSimilarPost(this)
$local(".new-post-title").blur ->
handleSimilarPost(this)
$local(".hide-similar-posts").click ->
$local(".new-post-similar-posts-wrapper").hide()
$local(".hide-similar-posts").click ->
$local(".new-post-similar-posts-wrapper").hide()
$local(".discussion-submit-post").click ->
handleSubmitNewPost(this)
$local(".discussion-cancel-post").click ->
handleCancelNewPost(this)
$local(".discussion-submit-post").click ->
handleSubmitNewPost(this)
$local(".discussion-cancel-post").click ->
handleCancelNewPost(this)
$(elem).hide()
#$(elem).hide()
handleAjaxReloadDiscussion = (elem, url) ->
$elem = $(elem)
......@@ -151,6 +151,8 @@ initializeFollowDiscussion = (discussion) ->
$elem = $(elem)
url = $elem.attr("page-url")
handleAjaxReloadDiscussion($elem, url)
initializeNewPost()
Discussion.bindLocalEvents $local,
......@@ -158,8 +160,8 @@ initializeFollowDiscussion = (discussion) ->
event.preventDefault()
handleAjaxSearch(this)
"click .discussion-new-post": ->
handleNewPost(this)
#"click .discussion-new-post": ->
# handleNewPost(this)
"click .discussion-search-link": ->
handleAjaxSearch($local(".search-wrapper>.discussion-search-form"))
......
......@@ -116,20 +116,26 @@ wmdEditors = {}
appended_id = "-#{cls_identifier}-#{id}"
imageUploadUrl = Discussion.urlFor('upload')
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.postMathJaxProcessor
console.log editor
wmdEditors["#{cls_identifier}-#{id}"] = editor
$input = $("#wmd-input-#{cls_identifier}-#{id}")
$input.attr("placeholder", "post a new topic...").bind 'focus', (e) ->
$('.new-post-form').removeClass('collapsed')
editor
getWmdEditor: ($content, $local, cls_identifier) ->
id = $content.attr("_id")
wmdEditors["#{cls_identifier}-#{id}"]
getWmdContent: ($content, $local, cls_identifier) ->
getWmdInput: ($content, $local, cls_identifier) ->
id = $content.attr("_id")
$local("#wmd-input-#{cls_identifier}-#{id}").val()
$local("#wmd-input-#{cls_identifier}-#{id}")
getWmdContent: ($content, $local, cls_identifier) ->
Discussion.getWmdInput($content, $local, cls_identifier).val()
setWmdContent: ($content, $local, cls_identifier, text) ->
id = $content.attr("_id")
$local("#wmd-input-#{cls_identifier}-#{id}").val(text)
Discussion.getWmdInput($content, $local, cls_identifier).val(text)
wmdEditors["#{cls_identifier}-#{id}"].refreshPreview()
getContentInfo: (id, attr) ->
......
......@@ -92,6 +92,10 @@ div.course-wrapper {
list-style: none;
padding-left: 2em;
}
&.admin-actions {
list-style: none;
}
}
nav.sequence-bottom {
......
<%! from django.core.urlresolvers import reverse %>
<%
def url_for(commentable):
return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id, commentable['discussion_id']])
%>
<article class="sidebar-module">
<a href="#" class="sidebar-new-post-button">New Post</a>
</article>
<%def name="make_category(category, commentables)">
<h3><a href="#">${category}</a></h3>
<article class="discussion-sidebar-following sidebar-module">
<header>
<h4>Following</h4>
<a href="#" class="sidebar-view-all">view all &rsaquo;</a>
</header>
<ol class="discussion-sidebar-following-list">
<li><a href="#"><span class="sidebar-following-name">Hints on HW4</span> <span class="sidebar-vote-count">1348</span></a></li>
<li><a href="#"><span class="sidebar-following-name">Answers to Mi-term</span> <span class="sidebar-vote-count">-37</span></a></li>
<li><a href="#"><span class="sidebar-following-name">Summaries for Lectures 1 to 12 (more added, links fixed)</span> <span class="sidebar-vote-count">128</span></a></li>
<ol>
</article>
<ul>
% for commentable in commentables:
<li${' class="active"' if active == commentable['discussion_id'] else ''}>
<a href="${url_for(commentable)}">
<p>${commentable['title']}</p>
</a>
% endfor
</ul>
</%def>
% for category, commentables in discussion_info.items():
${make_category(category, commentables)}
% endfor
<article class="discussion-sidebar-tags sidebar-module">
<header>
<h4>Recent Tags</h4>
</header>
<ol class="discussion-sidebar-tags-list">
<li><a href="#" class="thread-tag">week-1</a><span class="sidebar-tag-count">&times;1348</span></li>
<li><a href="#" class="thread-tag">S2V8: Method 3</a><span class="sidebar-tag-count">&times;37</span></li>
<li><a href="#" class="thread-tag">artificial-intelligence</a><span class="sidebar-tag-count">&times;128</span></li>
<ol>
</article>
\ No newline at end of file
<%namespace name="renderer" file="_thread.html"/>
<section class="discussion inline-discussion" _id="${discussion_id}">
<div class="discussion-non-content discussion-local">
<div class="discussion-title-wrapper">
<a class="discussion-title" href="javascript:void(0)">Discussion</a>
</div>
<div class="search-wrapper">
<%include file="_search_bar.html" />
</div>
<div class="search-within">
<input type="checkbox" id="discussion-search-within-board-${discussion_id}" class="discussion-search-within-board" checked/>
<label for="discussion-search-within-board-${discussion_id}" class="discussion-search-within-board">Search within board</label>
</div>
<div class="discussion-new-post control-button" href="javascript:void(0)">New Post</div>
</div>
</div>
% if len(threads) == 0:
<div class="blank">
......
<%namespace name="renderer" file="_thread.html"/>
<section class="discussion inline-discussion" _id="${discussion_id}">
<div class="discussion-non-content discussion-local">
<div class="search-wrapper-inline search-wrapper">
<%include file="_search_bar.html" />
</div>
<div class="discussion-new-post control-button" href="javascript:void(0)">New Post</div>
<form class="new-post-form collapsed" id="new-post-form" style="display: block; ">
<ul class="discussion-errors"></ul>
<div class="new-post-body reply-body"></div>
<div class="post-options">
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-${discussion_id}">
<label for="discussion-post-anonymously-${discussion_id}">post anonymously</label>
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-${discussion_id}" checked="">
<label for="discussion-auto-watch-${discussion_id}">follow this thread</label>
</div>
<div class="reply-post-control">
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
</div>
</form>
</div>
<div class="threads">
% for thread in threads:
${renderer.render_thread(course_id, thread, show_comments=False)}
% endfor
</div>
% if len(threads) == 0:
<div class="blank">
<%include file="_blank_slate.html" />
</div>
<div class="threads"></div>
% else:
<%include file="_sort.html" />
<div class="threads">
% for thread in threads:
${renderer.render_thread(course_id, thread, show_comments=False)}
% endfor
</div>
<%include file="_paginator.html" />
% endif
<%include file="_paginator.html" />
</section>
<%!
......@@ -36,3 +40,5 @@
}
$$annotated_content_info = $.extend($$annotated_content_info, JSON.parse("${annotated_content_info | escape_quotes}"));
</script>
......@@ -3,9 +3,9 @@
<%def name="link_to_sort(key, title)">
% if key == sort_key:
% if sort_order.lower() == 'desc':
${_link_to_sort(key, 'asc', title + ' &#x25BC;', 'sorted')}
${_link_to_sort(key, 'asc', title + '', 'sorted')}
% else:
${_link_to_sort(key, 'desc', title + ' &#x25B2;', 'sorted')}
${_link_to_sort(key, 'desc', title + '', 'sorted')}
% endif
% else:
${_link_to_sort(key, 'desc', title)}
......@@ -22,14 +22,14 @@
%>
<a class="discussion-sort-link ${cls}" href="javascript:void(0)" sort-url="${url_for_sort(key, order)}">${title}</a>
</%def>
<div class="discussion-sort discussion-local">
Sort by:
<span class="discussion-label">Sort by:</span>
${link_to_sort('top', 'top')}
${link_to_sort('date', 'date')}
|
${link_to_sort('activity', 'activity')}
|
${link_to_sort('votes', 'votes')}
|
${link_to_sort('comments', 'comments')}
</div>
......@@ -36,10 +36,17 @@
<%def name="render_content(content, type, **kwargs)">
<div class="discussion-content">
<div class="discussion-content-wrapper clearfix">
<div class="follow-wrapper">
</div>
${render_vote(content)}
<div class="discussion-right-wrapper clearfix">
<ul class="admin-actions">
% if type == 'comment':
<li><a href="javascript:void(0)" class="admin-endorse">Endorse</a></li>
% endif
<li><a href="javascript:void(0)" class="admin-edit">Edit</a></li>
<li><a href="javascript:void(0)" class="admin-delete">Delete</a></li>
</ul>
${render_title(content, type, **kwargs)}
<div class="discussion-content-view">
<a name="${content['id']}"></a>
......@@ -84,33 +91,23 @@
</%def>
<%def name="render_bottom_bar(content, type, **kwargs)">
<div class="info">
${render_info(content)}
${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']}" />
% else:
<input type="checkbox" class="discussion-link discussion-endorse" id="discussion-endorse-${content['id']}" />
% endif
<label class="discussion-link" for="discussion-endorse-${content['id']}">Endorsed</label>
</span>
% 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>
<div class="info">
${render_info(content)}
<ul class="discussion-actions">
<li>${render_link("discussion-link discussion-reply discussion-reply-" + type, "Reply")}</li>
<li><div class="follow-wrapper"></div></li>
<li>${render_link("discussion-link discussion-edit", "Edit")}</li>
<li>
% if type == "comment" and request.user.is_staff:
% if content['endorsed']:
<input type="checkbox" checked="checked" class="discussion-link discussion-endorse" id="discussion-endorse-${content['id']}">
% else:
<input type="checkbox" class="discussion-link discussion-endorse" id="discussion-endorse-${content['id']}">
% endif
<label class="discussion-link" for="discussion-endorse-${content['id']}">Endorsed</label>
% endif
% endif
${render_link("discussion-link discussion-delete", "Delete")}
</li>
</ul>
</div>
</%def>
......@@ -125,7 +122,13 @@
</div>
<div class="comment-count">
% if content.get('comments_count', -1) >= 0:
<a href="javascript:void(0)" class="discussion-show-comments"> Show ${content['comments_count']} comment(s)</a>
<a href="javascript:void(0)" class="discussion-show-comments">
% if content.get('comments_count', -1) >= 2:
Show ${content['comments_count']} comments
% else:
Show ${content['comments_count']} comment
% endif
</a>
% endif
</div>
</%def>
......@@ -136,8 +139,8 @@
<%def name="render_vote(content)">
<div class="discussion-votes">
${render_link("discussion-vote discussion-vote-up", "&#x2C4;")}
${render_link("discussion-vote discussion-vote-up", "&#9650;")}
<div class="discussion-votes-point">${content['votes']['point']}</div>
${render_link("discussion-vote discussion-vote-down", "&#x2C5;")}
${render_link("discussion-vote discussion-vote-down", "&#9660;")}
</div>
</%def>
......@@ -2,14 +2,14 @@ ${module_content}
%if edit_link:
<div><a href="${edit_link}">Edit</a></div>
% endif
<div><a href="javascript:void(0)" onclick="javascript:$('#${element_id}_debug').slideToggle()">Staff Debug Info</a></div>
<div class="staff_info">
<a href="javascript:void(0)" onclick="javascript:$('#${element_id}_debug').toggle()">Staff Debug Info</a>
<span style="display:none" id="${element_id}_debug">
<div class="staff_info">
definition = <pre>${definition | h}</pre>
metadata = ${metadata | h}
</span>
</div>
%if render_histogram:
<div id="histogram_${element_id}" class="histogram" data-histogram="${histogram}"></div>
%endif
</span>
......@@ -142,7 +142,6 @@ if settings.COURSEWARE_ENABLED:
# discussion
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/',
include('django_comment_client.urls')),
# For the instructor
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$',
'courseware.views.gradebook'),
......
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