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 = ( ...@@ -121,6 +121,7 @@ MIDDLEWARE_CLASSES = (
) )
############################ SIGNAL HANDLERS ################################ ############################ SIGNAL HANDLERS ################################
# This is imported to register the exception signal handling that logs exceptions
import monitoring.exceptions # noqa import monitoring.exceptions # noqa
############################ DJANGO_BUILTINS ################################ ############################ 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): ...@@ -142,7 +142,7 @@ def get_module(user, request, location, student_module_cache, position=None):
shared_module = 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 shared_state = shared_module.state if shared_module is not None else None
# TODO (vshnayder): fix hardcoded urls (use reverse) # TODO (vshnayder): fix hardcoded urls (use reverse)
......
...@@ -34,7 +34,7 @@ def permitted(fn): ...@@ -34,7 +34,7 @@ def permitted(fn):
content = None content = None
return content 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) return fn(request, *args, **kwargs)
else: else:
return JsonError("unauthorized") return JsonError("unauthorized")
......
...@@ -54,7 +54,7 @@ def render_discussion(request, course_id, threads, discussion_id=None, \ ...@@ -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])), 'forum': (lambda: reverse('django_comment_client.forum.views.forum_form_discussion', args=[course_id, discussion_id])),
}[discussion_type]() }[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 = { context = {
'threads': threads, 'threads': threads,
...@@ -153,18 +153,18 @@ def forum_form_discussion(request, course_id, discussion_id): ...@@ -153,18 +153,18 @@ def forum_form_discussion(request, course_id, discussion_id):
return render_to_response('discussion/index.html', context) 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 { return {
'editable': check_permissions_by_view(user, content, "update_thread" if is_thread else "update_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, content, "create_comment" if is_thread else "create_sub_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, content, "endorse_comment") if not is_thread else False, '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, content, "delete_thread" if is_thread else "delete_comment"), '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 = {} infos = {}
def _annotate(content, is_thread=is_thread): 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', []): for child in content.get('children', []):
_annotate(child, is_thread=False) _annotate(child, is_thread=False)
_annotate(thread) _annotate(thread)
...@@ -175,7 +175,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id): ...@@ -175,7 +175,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id):
thread = cc.Thread.find(thread_id).retrieve_with_comments() thread = cc.Thread.find(thread_id).retrieve_with_comments()
#comment_client.get_thread(thread_id, recursive=True) #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) user=request.user, is_thread=True)
context = { context = {
...@@ -193,7 +193,7 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -193,7 +193,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if request.is_ajax(): if request.is_ajax():
thread = cc.Thread.find(thread_id).retrieve_with_comments()#comment_client.get_thread(thread_id, recursive=True) 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)} context = {'thread': dict(thread)}
html = render_to_string('discussion/_ajax_single_thread.html', context) 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): ...@@ -10,7 +10,9 @@ class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Adding model 'Role' # Adding model 'Role'
db.create_table('django_comment_client_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']) db.send_create_signal('django_comment_client', ['Role'])
...@@ -28,14 +30,6 @@ class Migration(SchemaMigration): ...@@ -28,14 +30,6 @@ class Migration(SchemaMigration):
)) ))
db.send_create_signal('django_comment_client', ['Permission']) 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' # Adding M2M table for field roles on 'Permission'
db.create_table('django_comment_client_permission_roles', ( db.create_table('django_comment_client_permission_roles', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
...@@ -55,9 +49,6 @@ class Migration(SchemaMigration): ...@@ -55,9 +49,6 @@ class Migration(SchemaMigration):
# Deleting model 'Permission' # Deleting model 'Permission'
db.delete_table('django_comment_client_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' # Removing M2M table for field roles on 'Permission'
db.delete_table('django_comment_client_permission_roles') db.delete_table('django_comment_client_permission_roles')
...@@ -127,12 +118,13 @@ class Migration(SchemaMigration): ...@@ -127,12 +118,13 @@ class Migration(SchemaMigration):
'django_comment_client.permission': { 'django_comment_client.permission': {
'Meta': {'object_name': 'Permission'}, 'Meta': {'object_name': 'Permission'},
'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}), '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']"}), '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': { 'django_comment_client.role': {
'Meta': {'object_name': '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']"}) 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
} }
} }
......
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
import logging
class Role(models.Model): 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") users = models.ManyToManyField(User, related_name="roles")
course_id = models.CharField(max_length=255, blank=True, db_index=True)
def __unicode__(self): def __unicode__(self):
return self.name return self.name + " for " + (self.course_id if self.course_id else "all courses")
@staticmethod def inherit_permissions(self, role):
def register(name): if role.course_id and role.course_id != self.course_id:
return Role.objects.get_or_create(name=name)[0] 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): def add_permission(self, permission):
for p in permissions: self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
if not self.permissions.filter(name=p):
self.permissions.add(Permission.register(p))
def inherit_permissions(self, role): def has_permission(self, permission):
self.register_permissions(map(lambda p: p.name, role.permissions.all())) return self.permissions.filter(name=permission).exists()
class Permission(models.Model): class Permission(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, primary_key=True)
users = models.ManyToManyField(User, related_name="permissions")
roles = models.ManyToManyField(Role, related_name="permissions") roles = models.ManyToManyField(Role, related_name="permissions")
def __unicode__(self): def __unicode__(self):
return self.name 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 ...@@ -2,110 +2,98 @@ from .models import Role, Permission
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from student.models import CourseEnrollment
import logging import logging
def has_permission(user, p):
if not Permission.objects.filter(name=p).exists(): @receiver(post_save, sender=CourseEnrollment)
logging.warning("Permission %s was not registered. " % p) def assign_default_role(sender, instance, **kwargs):
if Permission.objects.filter(users=user, name=p).exists(): if instance.user.is_staff:
return True role = Role.objects.get(course_id=instance.course_id, name="Moderator")
if Permission.objects.filter(roles__in=user.roles.all(), name=p).exists(): else:
return True 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 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): CONDITIONS = ['is_open', 'is_author']
permission = Permission.register(name=p) def check_condition(user, condition, course_id, data):
if isinstance(instance, User) or isinstance(isinstance, Role): def check_open(user, condition, course_id, data):
instance.permissions.add(permission) return not data['content']['closed']
else:
raise TypeError("Permission can only be added to a role or user")
def check_author(user, condition, course_id, data):
return data['content']['user_id'] == str(user.id)
@receiver(post_save, sender=User) handlers = {
def assign_default_role(sender, instance, **kwargs): 'is_open' : check_open,
# if kwargs.get("created", True): 'is_author' : check_author,
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)
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. 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 "can_view" or "can_edit" permission. To use AND operator in between, wrap them in
a list: 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']
""" """
permissions = filter(lambda x: len(x), list(per))
def test(user, per, operator="or"):
def test_permission(user, permission, operator="or"): if isinstance(per, basestring):
if isinstance(permission, basestring): if per in CONDITIONS:
# import pdb; pdb.set_trace() return check_condition(user, per, course_id, kwargs)
if permission == "": return has_permission(user, per, course_id=course_id)
return True elif isinstance(per, list) and operator in ["and", "or"]:
elif permission == "author": results = [test(user, x, operator="and") for x in per]
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]
if operator == "or": if operator == "or":
return True in results return True in results
elif operator == "and": elif operator == "and":
return not False in results return not False in results
return test_permission(user, permissions, operator="or") return test(user, permissions, operator="or")
VIEW_PERMISSIONS = { VIEW_PERMISSIONS = {
'update_thread' : ('edit_content', ['update_thread', 'open', 'author']), 'update_thread' : ['edit_content', ['update_thread', 'is_open', 'author']],
'create_comment' : (["create_comment", "open"]), # 'create_comment' : [["create_comment", "is_open"]],
'delete_thread' : ('delete_thread'), 'create_comment' : ["create_comment"],
'update_comment' : ('edit_content', ['update_comment', 'open', 'author']), 'delete_thread' : ['delete_thread'],
'endorse_comment' : ('endorse_comment'), 'update_comment' : ['edit_content', ['update_comment', 'is_open', 'author']],
'openclose_thread' : ('openclose_thread'), 'endorse_comment' : ['endorse_comment'],
'create_sub_comment': (['create_sub_comment', 'open']), 'openclose_thread' : ['openclose_thread'],
'delete_comment' : ('delete_comment'), 'create_sub_comment': [['create_sub_comment', 'is_open']],
'vote_for_comment' : (['vote', 'open']), 'delete_comment' : ['delete_comment'],
'undo_vote_for_comment': (['unvote', 'open']), 'vote_for_comment' : [['vote', 'is_open']],
'vote_for_thread' : (['vote', 'open']), 'undo_vote_for_comment': [['unvote', 'is_open']],
'undo_vote_for_thread': (['unvote', 'open']), 'vote_for_thread' : [['vote', 'is_open']],
'follow_thread' : ('follow_thread'), 'undo_vote_for_thread': [['unvote', 'is_open']],
'follow_commentable': ('follow_commentable'), 'follow_thread' : ['follow_thread'],
'follow_user' : ('follow_user'), 'follow_commentable': ['follow_commentable'],
'unfollow_thread' : ('unfollow_thread'), 'follow_user' : ['follow_user'],
'unfollow_commentable': ('unfollow_commentable'), 'unfollow_thread' : ['unfollow_thread'],
'unfollow_user' : ('unfollow_user'), 'unfollow_commentable': ['unfollow_commentable'],
'create_thread' : ('create_thread'), '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: try:
p = VIEW_PERMISSIONS[name] p = VIEW_PERMISSIONS[name]
except KeyError: except KeyError:
logging.warning("Permission for view named %s does not exist in permissions.py" % name) 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_conditions_permissions(user, p, course_id, content=content)
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
...@@ -174,6 +174,9 @@ MODULESTORE = { ...@@ -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 ############################### ############################### DJANGO BUILT-INS ###############################
# Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here # Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
...@@ -286,7 +289,6 @@ TEMPLATE_LOADERS = ( ...@@ -286,7 +289,6 @@ TEMPLATE_LOADERS = (
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
'util.middleware.ExceptionLoggingMiddleware',
'django_comment_client.middleware.AjaxExceptionMiddleware', 'django_comment_client.middleware.AjaxExceptionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', '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) -> ...@@ -183,6 +183,8 @@ initializeFollowThread = (thread) ->
handleEndorse = (elem, endorsed) -> handleEndorse = (elem, endorsed) ->
url = Discussion.urlFor('endorse_comment', id) url = Discussion.urlFor('endorse_comment', id)
console.log endorsed
console.log url
Discussion.safeAjax Discussion.safeAjax
$elem: $(elem) $elem: $(elem)
url: url url: url
...@@ -196,6 +198,9 @@ initializeFollowThread = (thread) -> ...@@ -196,6 +198,9 @@ initializeFollowThread = (thread) ->
else else
$(content).removeClass("endorsed") $(content).removeClass("endorsed")
$(elem).unbind('click').click ->
handleEndorse(elem, !endorsed)
handleOpenClose = (elem, text) -> handleOpenClose = (elem, text) ->
url = Discussion.urlFor('openclose_thread', id) url = Discussion.urlFor('openclose_thread', id)
closed = undefined closed = undefined
...@@ -314,19 +319,19 @@ initializeFollowThread = (thread) -> ...@@ -314,19 +319,19 @@ initializeFollowThread = (thread) ->
else else
handleVote($elem, "down") handleVote($elem, "down")
"click .discussion-endorse": -> "click .admin-endorse": ->
handleEndorse(this, $(this).is(":checked")) handleEndorse(this, true)#, $(this).is(":checked"))
"click .discussion-openclose": -> "click .discussion-openclose": ->
handleOpenClose(this, $(this).text()) handleOpenClose(this, $(this).text())
"click .discussion-edit": -> "click .admin-edit": ->
if $content.hasClass("thread") if $content.hasClass("thread")
handleEditThread(this) handleEditThread(this)
else else
handleEditComment(this) handleEditComment(this)
"click .discussion-delete": -> "click .admin-delete": ->
handleDelete(this) handleDelete(this)
initializeContent: (content) -> initializeContent: (content) ->
......
...@@ -36,7 +36,7 @@ initializeFollowDiscussion = (discussion) -> ...@@ -36,7 +36,7 @@ initializeFollowDiscussion = (discussion) ->
title = $local(".new-post-title").val() title = $local(".new-post-title").val()
body = Discussion.getWmdContent $discussion, $local, "new-post-body" body = Discussion.getWmdContent $discussion, $local, "new-post-body"
tags = $local(".new-post-tags").val() 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 Discussion.safeAjax
$elem: $(elem) $elem: $(elem)
url: url url: url
...@@ -95,33 +95,33 @@ initializeFollowDiscussion = (discussion) -> ...@@ -95,33 +95,33 @@ initializeFollowDiscussion = (discussion) ->
$wrapper.hide() $wrapper.hide()
$title.attr("prev-text", text) $title.attr("prev-text", text)
handleNewPost = (elem) -> initializeNewPost = (elem) ->
newPostForm = $local(".new-post-form") #newPostForm = $local(".new-post-form")
if newPostForm.length #view = { discussion_id: id }
newPostForm.show() #$newPostButton = $local(".discussion-new-post")
$(elem).hide() #$newPostButton.after Mustache.render Discussion.newPostTemplate, view
else newPostBody = $discussion.find(".new-post-body")
view = { discussion_id: id } if newPostBody.length
$newPostButton = $local(".discussion-new-post") Discussion.makeWmdEditor $discussion, $local, "new-post-body"
$newPostButton.after Mustache.render Discussion.newPostTemplate, view
newPostBody = $discussion.find(".new-post-body") $input = Discussion.getWmdInput($discussion, $local, "new-post-body")
if newPostBody.length $input.attr("placeholder", "post a new topic...").bind 'focus', (e) ->
Discussion.makeWmdEditor $discussion, $local, "new-post-body" $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 -> $local(".new-post-title").blur ->
handleSimilarPost(this) handleSimilarPost(this)
$local(".hide-similar-posts").click -> $local(".hide-similar-posts").click ->
$local(".new-post-similar-posts-wrapper").hide() $local(".new-post-similar-posts-wrapper").hide()
$local(".discussion-submit-post").click -> $local(".discussion-submit-post").click ->
handleSubmitNewPost(this) handleSubmitNewPost(this)
$local(".discussion-cancel-post").click -> $local(".discussion-cancel-post").click ->
handleCancelNewPost(this) handleCancelNewPost(this)
$(elem).hide() #$(elem).hide()
handleAjaxReloadDiscussion = (elem, url) -> handleAjaxReloadDiscussion = (elem, url) ->
$elem = $(elem) $elem = $(elem)
...@@ -151,6 +151,8 @@ initializeFollowDiscussion = (discussion) -> ...@@ -151,6 +151,8 @@ initializeFollowDiscussion = (discussion) ->
$elem = $(elem) $elem = $(elem)
url = $elem.attr("page-url") url = $elem.attr("page-url")
handleAjaxReloadDiscussion($elem, url) handleAjaxReloadDiscussion($elem, url)
initializeNewPost()
Discussion.bindLocalEvents $local, Discussion.bindLocalEvents $local,
...@@ -158,8 +160,8 @@ initializeFollowDiscussion = (discussion) -> ...@@ -158,8 +160,8 @@ initializeFollowDiscussion = (discussion) ->
event.preventDefault() event.preventDefault()
handleAjaxSearch(this) handleAjaxSearch(this)
"click .discussion-new-post": -> #"click .discussion-new-post": ->
handleNewPost(this) # handleNewPost(this)
"click .discussion-search-link": -> "click .discussion-search-link": ->
handleAjaxSearch($local(".search-wrapper>.discussion-search-form")) handleAjaxSearch($local(".search-wrapper>.discussion-search-form"))
......
...@@ -116,20 +116,26 @@ wmdEditors = {} ...@@ -116,20 +116,26 @@ wmdEditors = {}
appended_id = "-#{cls_identifier}-#{id}" appended_id = "-#{cls_identifier}-#{id}"
imageUploadUrl = Discussion.urlFor('upload') imageUploadUrl = Discussion.urlFor('upload')
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.postMathJaxProcessor editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.postMathJaxProcessor
console.log editor
wmdEditors["#{cls_identifier}-#{id}"] = 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 editor
getWmdEditor: ($content, $local, cls_identifier) -> getWmdEditor: ($content, $local, cls_identifier) ->
id = $content.attr("_id") id = $content.attr("_id")
wmdEditors["#{cls_identifier}-#{id}"] wmdEditors["#{cls_identifier}-#{id}"]
getWmdContent: ($content, $local, cls_identifier) -> getWmdInput: ($content, $local, cls_identifier) ->
id = $content.attr("_id") 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) -> setWmdContent: ($content, $local, cls_identifier, text) ->
id = $content.attr("_id") Discussion.getWmdInput($content, $local, cls_identifier).val(text)
$local("#wmd-input-#{cls_identifier}-#{id}").val(text)
wmdEditors["#{cls_identifier}-#{id}"].refreshPreview() wmdEditors["#{cls_identifier}-#{id}"].refreshPreview()
getContentInfo: (id, attr) -> getContentInfo: (id, attr) ->
......
...@@ -92,6 +92,10 @@ div.course-wrapper { ...@@ -92,6 +92,10 @@ div.course-wrapper {
list-style: none; list-style: none;
padding-left: 2em; padding-left: 2em;
} }
&.admin-actions {
list-style: none;
}
} }
nav.sequence-bottom { nav.sequence-bottom {
......
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<% <article class="sidebar-module">
def url_for(commentable): <a href="#" class="sidebar-new-post-button">New Post</a>
return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id, commentable['discussion_id']]) </article>
%>
<%def name="make_category(category, commentables)"> <article class="discussion-sidebar-following sidebar-module">
<h3><a href="#">${category}</a></h3> <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> <article class="discussion-sidebar-tags sidebar-module">
% for commentable in commentables: <header>
<li${' class="active"' if active == commentable['discussion_id'] else ''}> <h4>Recent Tags</h4>
<a href="${url_for(commentable)}"> </header>
<p>${commentable['title']}</p> <ol class="discussion-sidebar-tags-list">
</a> <li><a href="#" class="thread-tag">week-1</a><span class="sidebar-tag-count">&times;1348</span></li>
% endfor <li><a href="#" class="thread-tag">S2V8: Method 3</a><span class="sidebar-tag-count">&times;37</span></li>
</ul> <li><a href="#" class="thread-tag">artificial-intelligence</a><span class="sidebar-tag-count">&times;128</span></li>
</%def> <ol>
</article>
% for category, commentables in discussion_info.items(): \ No newline at end of file
${make_category(category, commentables)}
% endfor
<%namespace name="renderer" file="_thread.html"/> <%namespace name="renderer" file="_thread.html"/>
<section class="discussion inline-discussion" _id="${discussion_id}"> <section class="discussion inline-discussion" _id="${discussion_id}">
<div class="discussion-non-content discussion-local"> <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"> <div class="search-wrapper">
<%include file="_search_bar.html" /> <%include file="_search_bar.html" />
</div> </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: % if len(threads) == 0:
<div class="blank"> <div class="blank">
......
<%namespace name="renderer" file="_thread.html"/> <%namespace name="renderer" file="_thread.html"/>
<section class="discussion inline-discussion" _id="${discussion_id}"> <section class="discussion inline-discussion" _id="${discussion_id}">
<div class="discussion-non-content discussion-local"> <div class="discussion-non-content discussion-local">
<div class="search-wrapper-inline search-wrapper"> <form class="new-post-form collapsed" id="new-post-form" style="display: block; ">
<%include file="_search_bar.html" /> <ul class="discussion-errors"></ul>
</div> <div class="new-post-body reply-body"></div>
<div class="discussion-new-post control-button" href="javascript:void(0)">New Post</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> </div>
% if len(threads) == 0:
<div class="blank"> <%include file="_paginator.html" />
<%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
</section> </section>
<%! <%!
...@@ -36,3 +40,5 @@ ...@@ -36,3 +40,5 @@
} }
$$annotated_content_info = $.extend($$annotated_content_info, JSON.parse("${annotated_content_info | escape_quotes}")); $$annotated_content_info = $.extend($$annotated_content_info, JSON.parse("${annotated_content_info | escape_quotes}"));
</script> </script>
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
<%def name="link_to_sort(key, title)"> <%def name="link_to_sort(key, title)">
% if key == sort_key: % if key == sort_key:
% if sort_order.lower() == 'desc': % if sort_order.lower() == 'desc':
${_link_to_sort(key, 'asc', title + ' &#x25BC;', 'sorted')} ${_link_to_sort(key, 'asc', title + '', 'sorted')}
% else: % else:
${_link_to_sort(key, 'desc', title + ' &#x25B2;', 'sorted')} ${_link_to_sort(key, 'desc', title + '', 'sorted')}
% endif % endif
% else: % else:
${_link_to_sort(key, 'desc', title)} ${_link_to_sort(key, 'desc', title)}
...@@ -22,14 +22,14 @@ ...@@ -22,14 +22,14 @@
%> %>
<a class="discussion-sort-link ${cls}" href="javascript:void(0)" sort-url="${url_for_sort(key, order)}">${title}</a> <a class="discussion-sort-link ${cls}" href="javascript:void(0)" sort-url="${url_for_sort(key, order)}">${title}</a>
</%def> </%def>
<div class="discussion-sort discussion-local"> <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('date', 'date')}
|
${link_to_sort('activity', 'activity')}
|
${link_to_sort('votes', 'votes')} ${link_to_sort('votes', 'votes')}
|
${link_to_sort('comments', 'comments')} ${link_to_sort('comments', 'comments')}
</div> </div>
...@@ -36,10 +36,17 @@ ...@@ -36,10 +36,17 @@
<%def name="render_content(content, type, **kwargs)"> <%def name="render_content(content, type, **kwargs)">
<div class="discussion-content"> <div class="discussion-content">
<div class="discussion-content-wrapper clearfix"> <div class="discussion-content-wrapper clearfix">
<div class="follow-wrapper">
</div>
${render_vote(content)} ${render_vote(content)}
<div class="discussion-right-wrapper clearfix"> <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)} ${render_title(content, type, **kwargs)}
<div class="discussion-content-view"> <div class="discussion-content-view">
<a name="${content['id']}"></a> <a name="${content['id']}"></a>
...@@ -84,33 +91,23 @@ ...@@ -84,33 +91,23 @@
</%def> </%def>
<%def name="render_bottom_bar(content, type, **kwargs)"> <%def name="render_bottom_bar(content, type, **kwargs)">
<div class="info"> <div class="info">
${render_info(content)} ${render_info(content)}
${render_link("discussion-link discussion-reply discussion-reply-" + type, "Reply")} <ul class="discussion-actions">
${render_link("discussion-link discussion-edit", "Edit")} <li>${render_link("discussion-link discussion-reply discussion-reply-" + type, "Reply")}</li>
<li><div class="follow-wrapper"></div></li>
% if type == 'thread': <li>${render_link("discussion-link discussion-edit", "Edit")}</li>
<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> <li>
% endif % if type == "comment" and request.user.is_staff:
<span class="discussion-endorse-control"> % if content['endorsed']:
% if content.get('endorsed', False): <input type="checkbox" checked="checked" class="discussion-link discussion-endorse" id="discussion-endorse-${content['id']}">
<input type="checkbox" checked="checked" class="discussion-link discussion-endorse" id="discussion-endorse-${content['id']}" /> % else:
% else: <input type="checkbox" class="discussion-link discussion-endorse" id="discussion-endorse-${content['id']}">
<input type="checkbox" class="discussion-link discussion-endorse" id="discussion-endorse-${content['id']}" /> % endif
% endif <label class="discussion-link" for="discussion-endorse-${content['id']}">Endorsed</label>
<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>
% endif % endif
% endif </li>
</ul>
${render_link("discussion-link discussion-delete", "Delete")}
</div> </div>
</%def> </%def>
...@@ -125,7 +122,13 @@ ...@@ -125,7 +122,13 @@
</div> </div>
<div class="comment-count"> <div class="comment-count">
% if content.get('comments_count', -1) >= 0: % 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 % endif
</div> </div>
</%def> </%def>
...@@ -136,8 +139,8 @@ ...@@ -136,8 +139,8 @@
<%def name="render_vote(content)"> <%def name="render_vote(content)">
<div class="discussion-votes"> <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> <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> </div>
</%def> </%def>
...@@ -2,14 +2,14 @@ ${module_content} ...@@ -2,14 +2,14 @@ ${module_content}
%if edit_link: %if edit_link:
<div><a href="${edit_link}">Edit</a></div> <div><a href="${edit_link}">Edit</a></div>
% endif % 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"> <span style="display:none" id="${element_id}_debug">
<div class="staff_info">
definition = <pre>${definition | h}</pre> definition = <pre>${definition | h}</pre>
metadata = ${metadata | h} metadata = ${metadata | h}
</span>
</div> </div>
%if render_histogram: %if render_histogram:
<div id="histogram_${element_id}" class="histogram" data-histogram="${histogram}"></div> <div id="histogram_${element_id}" class="histogram" data-histogram="${histogram}"></div>
%endif %endif
</span>
...@@ -142,7 +142,6 @@ if settings.COURSEWARE_ENABLED: ...@@ -142,7 +142,6 @@ if settings.COURSEWARE_ENABLED:
# discussion # discussion
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/',
include('django_comment_client.urls')), include('django_comment_client.urls')),
# For the instructor # For the instructor
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$',
'courseware.views.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