Commit f299ec26 by Tom Giannattasio

Merge branch 'master' into feature/rocha/wiki-cheatsheet

parents 5b4b4d7b a7ae8791
...@@ -46,4 +46,4 @@ class MakoMiddleware(object): ...@@ -46,4 +46,4 @@ class MakoMiddleware(object):
global requestcontext global requestcontext
requestcontext = RequestContext(request) requestcontext = RequestContext(request)
requestcontext['is_secure'] = request.is_secure() requestcontext['is_secure'] = request.is_secure()
requestcontext['site'] = settings.SITE_NAME requestcontext['site'] = request.get_host()
...@@ -273,7 +273,7 @@ def add_user_to_default_group(user, group): ...@@ -273,7 +273,7 @@ def add_user_to_default_group(user, group):
utg.users.add(User.objects.get(username=user)) utg.users.add(User.objects.get(username=user))
utg.save() utg.save()
# @receiver(post_save, sender=User) @receiver(post_save, sender=User)
def update_user_information(sender, instance, created, **kwargs): def update_user_information(sender, instance, created, **kwargs):
try: try:
cc_user = cc.User.from_django_user(instance) cc_user = cc.User.from_django_user(instance)
......
...@@ -511,7 +511,7 @@ def password_reset(request): ...@@ -511,7 +511,7 @@ def password_reset(request):
form.save(use_https = request.is_secure(), form.save(use_https = request.is_secure(),
from_email = settings.DEFAULT_FROM_EMAIL, from_email = settings.DEFAULT_FROM_EMAIL,
request = request, request = request,
domain_override = settings.SITE_NAME) domain_override = request.get_host())
return HttpResponse(json.dumps({'success':True, return HttpResponse(json.dumps({'success':True,
'value': render_to_string('registration/password_reset_done.html', {})})) 'value': render_to_string('registration/password_reset_done.html', {})}))
else: else:
......
...@@ -73,10 +73,12 @@ class CorrectMap(object): ...@@ -73,10 +73,12 @@ class CorrectMap(object):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] == test_key return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] == test_key
def get_npoints(self, answer_id): def get_npoints(self, answer_id):
if self.is_correct(answer_id): npoints = self.get_property(answer_id, 'npoints')
npoints = self.cmap[answer_id].get('npoints', 1) # default to 1 point if correct if npoints is not None:
return npoints or 1 return npoints
return 0 # if not correct, return 0 elif self.is_correct(answer_id):
return 1
return 0 # if not correct and no points have been assigned, return 0
def set_property(self, answer_id, property, value): def set_property(self, answer_id, property, value):
if answer_id in self.cmap: self.cmap[answer_id][property] = value if answer_id in self.cmap: self.cmap[answer_id][property] = value
......
...@@ -61,13 +61,12 @@ nav.sequence-nav { ...@@ -61,13 +61,12 @@ nav.sequence-nav {
min-width: 20px; min-width: 20px;
a { a {
width: 34px; width: 100%;
height: 34px; height: 42px;
margin: 4px auto; margin: 0;
background-position: center 10px; background-position: center 14px;
background-repeat: no-repeat; background-repeat: no-repeat;
border: 1px solid transparent; border: 1px solid transparent;
@include border-radius(35px);
cursor: pointer; cursor: pointer;
display: block; display: block;
padding: 0; padding: 0;
...@@ -82,7 +81,7 @@ nav.sequence-nav { ...@@ -82,7 +81,7 @@ nav.sequence-nav {
&:hover { &:hover {
background-color: #fff; background-color: #fff;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center 10px; background-position: center 14px;
} }
&.active { &.active {
...@@ -103,7 +102,7 @@ nav.sequence-nav { ...@@ -103,7 +102,7 @@ nav.sequence-nav {
&:hover { &:hover {
background-color: #fff; background-color: #fff;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center 10px; background-position: center 14px;
} }
} }
...@@ -298,14 +297,16 @@ nav.sequence-bottom { ...@@ -298,14 +297,16 @@ nav.sequence-bottom {
ul { ul {
@extend .clearfix; @extend .clearfix;
border: 1px solid $border-color;
@include border-radius(3px);
@include inline-block(); @include inline-block();
width: 100px; width: 103px;
li { li {
float: left; float: left;
width: 50%; width: 50px;
height: 44px;
border: 1px solid #ccc;
@include linear-gradient(top, #eee, #ddd);
@include box-shadow(0 1px 0 rgba(255, 255, 255, .7) inset);
&.prev, &.next { &.prev, &.next {
margin-bottom: 0; margin-bottom: 0;
...@@ -318,38 +319,39 @@ nav.sequence-bottom { ...@@ -318,38 +319,39 @@ nav.sequence-bottom {
padding: lh(.5) 4px; padding: lh(.5) 4px;
text-indent: -9999px; text-indent: -9999px;
@include transition(all, .2s, $ease-in-out-quad); @include transition(all, .2s, $ease-in-out-quad);
outline: 0;
&:hover { &:hover {
background-color: #ddd;
color: #000;
opacity: .5; opacity: .5;
text-decoration: none; background-position: center 15px;
} }
&.disabled { &.disabled {
opacity: .4; opacity: .4;
} }
&:focus {
outline: 0;
}
} }
} }
&.prev { &.prev {
border-radius: 35px 0 0 35px;
a { a {
background-image: url('../images/sequence-nav/previous-icon.png'); background-image: url('../images/sequence-nav/previous-icon.png');
border-right: 1px solid lighten(#c6c6c6, 10%); background-position: center 15px;
&:hover {
background-color: none;
}
} }
} }
&.next { &.next {
border-radius: 0 35px 35px 0;
border-left: none;
a { a {
background-image: url('../images/sequence-nav/next-icon.png'); background-image: url('../images/sequence-nav/next-icon.png');
background-position: center 15px;
&:hover {
background-color: none;
}
} }
} }
} }
...@@ -360,5 +362,12 @@ div.course-wrapper section.course-content ol.vert-mod > li ul.sequence-nav-butto ...@@ -360,5 +362,12 @@ div.course-wrapper section.course-content ol.vert-mod > li ul.sequence-nav-butto
list-style: none !important; list-style: none !important;
} }
.xmodule_SequenceModule nav.sequence-bottom ul li.next a,
.xmodule_SequenceModule nav.sequence-bottom ul li.prev a {
outline: 0;
&:focus {
outline: 0;
}
}
...@@ -55,6 +55,7 @@ class @Sequence ...@@ -55,6 +55,7 @@ class @Sequence
element.removeClass('progress-none') element.removeClass('progress-none')
.removeClass('progress-some') .removeClass('progress-some')
.removeClass('progress-done') .removeClass('progress-done')
switch progress switch progress
when 'none' then element.addClass('progress-none') when 'none' then element.addClass('progress-none')
when 'in_progress' then element.addClass('progress-some') when 'in_progress' then element.addClass('progress-some')
......
...@@ -5,6 +5,7 @@ import logging ...@@ -5,6 +5,7 @@ import logging
from path import path from path import path
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import Http404 from django.http import Http404
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
...@@ -133,8 +134,13 @@ def get_course_info_section(course, section_key): ...@@ -133,8 +134,13 @@ def get_course_info_section(course, section_key):
if section_key in ['handouts', 'guest_handouts', 'updates', 'guest_updates']: if section_key in ['handouts', 'guest_handouts', 'updates', 'guest_updates']:
try: try:
with course.system.resources_fs.open(path("info") / section_key + ".html") as htmlFile: with course.system.resources_fs.open(path("info") / section_key + ".html") as htmlFile:
return replace_urls(htmlFile.read().decode('utf-8'), # Replace '/static/' urls
course.metadata['data_dir']) info_html = replace_urls(htmlFile.read().decode('utf-8'), course.metadata['data_dir'])
# Replace '/course/' urls
course_root = reverse('course_root', args=[course.id])[:-1] # Remove trailing slash
info_html = replace_urls(info_html, course_root, '/course/')
return info_html
except ResourceNotFoundError: except ResourceNotFoundError:
log.exception("Missing info section {key} in course {url}".format( log.exception("Missing info section {key} in course {url}".format(
key=section_key, url=course.location.url())) key=section_key, url=course.location.url()))
......
...@@ -143,8 +143,9 @@ def get_module(user, request, location, student_module_cache, course_id, positio ...@@ -143,8 +143,9 @@ def get_module(user, request, location, student_module_cache, course_id, positio
exists. exists.
Arguments: Arguments:
- user : current django User - user : User for whom we're getting the module
- request : current django HTTPrequest - request : current django HTTPrequest -- used in particular for auth
(This is important e.g. for prof impersonation of students in progress view)
- location : A Location-like object identifying the module to load - location : A Location-like object identifying the module to load
- student_module_cache : a StudentModuleCache - student_module_cache : a StudentModuleCache
- course_id : the course_id in the context of which to load module - course_id : the course_id in the context of which to load module
...@@ -170,7 +171,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi ...@@ -170,7 +171,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
descriptor = modulestore().get_instance(course_id, location) descriptor = modulestore().get_instance(course_id, location)
# Short circuit--if the user shouldn't have access, bail without doing any work # Short circuit--if the user shouldn't have access, bail without doing any work
if not has_access(user, descriptor, 'load'): # NOTE: Do access check on request.user -- that's who actually needs access (e.g. could be prof
# impersonating a user)
if not has_access(request.user, descriptor, 'load'):
return None return None
#TODO Only check the cache if this module can possibly have state #TODO Only check the cache if this module can possibly have state
...@@ -199,7 +202,10 @@ def _get_module(user, request, location, student_module_cache, course_id, positi ...@@ -199,7 +202,10 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
) )
# Fully qualified callback URL for external queueing system # Fully qualified callback URL for external queueing system
xqueue_callback_url = request.build_absolute_uri('/')[:-1] # Trailing slash provided by reverse xqueue_callback_url = '{proto}://{host}'.format(
host=request.get_host(),
proto=request.META.get('HTTP_X_FORWARDED_PROTO', 'https' if request.is_secure() else 'http')
)
xqueue_callback_url += reverse('xqueue_callback', xqueue_callback_url += reverse('xqueue_callback',
kwargs=dict(course_id=course_id, kwargs=dict(course_id=course_id,
userid=str(user.id), userid=str(user.id),
...@@ -346,10 +352,10 @@ def xqueue_callback(request, course_id, userid, id, dispatch): ...@@ -346,10 +352,10 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
get = request.POST.copy() get = request.POST.copy()
for key in ['xqueue_header', 'xqueue_body']: for key in ['xqueue_header', 'xqueue_body']:
if not get.has_key(key): if not get.has_key(key):
return Http404 raise Http404
header = json.loads(get['xqueue_header']) header = json.loads(get['xqueue_header'])
if not isinstance(header, dict) or not header.has_key('lms_key'): if not isinstance(header, dict) or not header.has_key('lms_key'):
return Http404 raise Http404
# Retrieve target StudentModule # Retrieve target StudentModule
user = User.objects.get(id=userid) user = User.objects.get(id=userid)
......
...@@ -325,14 +325,17 @@ def progress(request, course_id, student_id=None): ...@@ -325,14 +325,17 @@ def progress(request, course_id, student_id=None):
raise Http404 raise Http404
student = User.objects.get(id=int(student_id)) student = User.objects.get(id=int(student_id))
# NOTE: To make sure impersonation by instructor works, use
# student instead of request.user in the rest of the function.
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course_id, request.user, course) course_id, student, course)
course_module = get_module(request.user, request, course.location, course_module = get_module(student, request, course.location,
student_module_cache, course_id) student_module_cache, course_id)
courseware_summary = grades.progress_summary(student, course_module, courseware_summary = grades.progress_summary(student, course_module,
course.grader, student_module_cache) course.grader, student_module_cache)
grade_summary = grades.grade(request.user, request, course, student_module_cache) grade_summary = grades.grade(student, request, course, student_module_cache)
context = {'course': course, context = {'course': course,
'courseware_summary': courseware_summary, 'courseware_summary': courseware_summary,
......
...@@ -51,11 +51,11 @@ def ajax_content_response(request, course_id, content, template_name): ...@@ -51,11 +51,11 @@ def ajax_content_response(request, course_id, content, template_name):
'content': content, 'content': content,
} }
html = render_to_string(template_name, context) html = render_to_string(template_name, context)
user_info = cc.User.from_django_user(request.user).safe_attributes() user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info) annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
return JsonResponse({ return JsonResponse({
'html': html, 'html': html,
'content': content, 'content': utils.safe_content(content),
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
}) })
...@@ -78,7 +78,7 @@ def create_thread(request, course_id, commentable_id): ...@@ -78,7 +78,7 @@ def create_thread(request, course_id, commentable_id):
if request.is_ajax(): if request.is_ajax():
return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_create_thread.html') return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_create_thread.html')
else: else:
return JsonResponse(thread.to_dict()) return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -90,7 +90,7 @@ def update_thread(request, course_id, thread_id): ...@@ -90,7 +90,7 @@ def update_thread(request, course_id, thread_id):
if request.is_ajax(): if request.is_ajax():
return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_update_thread.html') return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_update_thread.html')
else: else:
return JsonResponse(thread.to_dict()) return JsonResponse(utils.safe_content(thread.to_dict()))
def _create_comment(request, course_id, thread_id=None, parent_id=None): def _create_comment(request, course_id, thread_id=None, parent_id=None):
post = request.POST post = request.POST
...@@ -109,7 +109,7 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None): ...@@ -109,7 +109,7 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None):
if request.is_ajax(): if request.is_ajax():
return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_create_comment.html') return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_create_comment.html')
else: else:
return JsonResponse(comment.to_dict()) return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -126,7 +126,7 @@ def create_comment(request, course_id, thread_id): ...@@ -126,7 +126,7 @@ def create_comment(request, course_id, thread_id):
def delete_thread(request, course_id, thread_id): def delete_thread(request, course_id, thread_id):
thread = cc.Thread.find(thread_id) thread = cc.Thread.find(thread_id)
thread.delete() thread.delete()
return JsonResponse(thread.to_dict()) return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -138,7 +138,7 @@ def update_comment(request, course_id, comment_id): ...@@ -138,7 +138,7 @@ def update_comment(request, course_id, comment_id):
if request.is_ajax(): if request.is_ajax():
return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_update_comment.html') return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_update_comment.html')
else: else:
return JsonResponse(comment.to_dict()), return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -147,7 +147,7 @@ def endorse_comment(request, course_id, comment_id): ...@@ -147,7 +147,7 @@ def endorse_comment(request, course_id, comment_id):
comment = cc.Comment.find(comment_id) comment = cc.Comment.find(comment_id)
comment.endorsed = request.POST.get('endorsed', 'false').lower() == 'true' comment.endorsed = request.POST.get('endorsed', 'false').lower() == 'true'
comment.save() comment.save()
return JsonResponse(comment.to_dict()) return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -156,7 +156,11 @@ def openclose_thread(request, course_id, thread_id): ...@@ -156,7 +156,11 @@ def openclose_thread(request, course_id, thread_id):
thread = cc.Thread.find(thread_id) thread = cc.Thread.find(thread_id)
thread.closed = request.POST.get('closed', 'false').lower() == 'true' thread.closed = request.POST.get('closed', 'false').lower() == 'true'
thread.save() thread.save()
return JsonResponse(thread.to_dict()) thread = thread.to_dict()
return JsonResponse({
'content': utils.safe_content(thread),
'ability': utils.get_ability(course_id, thread, request.user),
})
@require_POST @require_POST
@login_required @login_required
...@@ -173,7 +177,7 @@ def create_sub_comment(request, course_id, comment_id): ...@@ -173,7 +177,7 @@ def create_sub_comment(request, course_id, comment_id):
def delete_comment(request, course_id, comment_id): def delete_comment(request, course_id, comment_id):
comment = cc.Comment.find(comment_id) comment = cc.Comment.find(comment_id)
comment.delete() comment.delete()
return JsonResponse(comment.to_dict()) return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -182,7 +186,7 @@ def vote_for_comment(request, course_id, comment_id, value): ...@@ -182,7 +186,7 @@ def vote_for_comment(request, course_id, comment_id, value):
user = cc.User.from_django_user(request.user) user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id) comment = cc.Comment.find(comment_id)
user.vote(comment, value) user.vote(comment, value)
return JsonResponse(comment.to_dict()) return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -191,7 +195,7 @@ def undo_vote_for_comment(request, course_id, comment_id): ...@@ -191,7 +195,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
user = cc.User.from_django_user(request.user) user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id) comment = cc.Comment.find(comment_id)
user.unvote(comment) user.unvote(comment)
return JsonResponse(comment.to_dict()) return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -200,7 +204,7 @@ def vote_for_thread(request, course_id, thread_id, value): ...@@ -200,7 +204,7 @@ def vote_for_thread(request, course_id, thread_id, value):
user = cc.User.from_django_user(request.user) user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id) thread = cc.Thread.find(thread_id)
user.vote(thread, value) user.vote(thread, value)
return JsonResponse(thread.to_dict()) return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -209,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id): ...@@ -209,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
user = cc.User.from_django_user(request.user) user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id) thread = cc.Thread.find(thread_id)
user.unvote(thread) user.unvote(thread)
return JsonResponse(thread.to_dict()) return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST @require_POST
......
...@@ -64,7 +64,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs): ...@@ -64,7 +64,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
'user': (lambda: reverse('django_comment_client.forum.views.user_profile', args=[course_id, user_id])), 'user': (lambda: reverse('django_comment_client.forum.views.user_profile', args=[course_id, user_id])),
}[discussion_type]() }[discussion_type]()
user_info = cc.User.from_django_user(request.user).safe_attributes() user_info = cc.User.from_django_user(request.user).to_dict()
def infogetter(thread): def infogetter(thread):
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info) return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
...@@ -83,7 +83,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs): ...@@ -83,7 +83,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
'base_url': base_url, 'base_url': base_url,
'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))), 'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))),
'annotated_content_info': json.dumps(annotated_content_info), 'annotated_content_info': json.dumps(annotated_content_info),
'discussion_data': json.dumps({ (discussion_id or user_id): threads }) 'discussion_data': json.dumps({ (discussion_id or user_id): map(utils.safe_content, threads) })
} }
context = dict(context.items() + query_params.items()) context = dict(context.items() + query_params.items())
return render_to_string(template, context) return render_to_string(template, context)
...@@ -128,7 +128,7 @@ def inline_discussion(request, course_id, discussion_id): ...@@ -128,7 +128,7 @@ def inline_discussion(request, course_id, discussion_id):
return utils.JsonResponse({ return utils.JsonResponse({
'html': html, 'html': html,
'discussionData': threads, 'discussion_data': map(utils.safe_content, threads),
}) })
def render_search_bar(request, course_id, discussion_id=None, text=''): def render_search_bar(request, course_id, discussion_id=None, text=''):
...@@ -149,7 +149,7 @@ def forum_form_discussion(request, course_id): ...@@ -149,7 +149,7 @@ def forum_form_discussion(request, course_id):
if request.is_ajax(): if request.is_ajax():
return utils.JsonResponse({ return utils.JsonResponse({
'html': content, 'html': content,
'discussionData': threads, 'discussion_data': map(utils.safe_content, threads),
}) })
else: else:
recent_active_threads = cc.search_recent_active_threads( recent_active_threads = cc.search_recent_active_threads(
...@@ -176,7 +176,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id): ...@@ -176,7 +176,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id):
thread = cc.Thread.find(thread_id).retrieve(recursive=True).to_dict() thread = cc.Thread.find(thread_id).retrieve(recursive=True).to_dict()
user_info = cc.User.from_django_user(request.user).safe_attributes() user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_infos(course_id, thread=thread, user=request.user, user_info=user_info) annotated_content_info = utils.get_annotated_content_infos(course_id, thread=thread, user=request.user, user_info=user_info)
...@@ -186,7 +186,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id): ...@@ -186,7 +186,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id):
'annotated_content_info': json.dumps(annotated_content_info), 'annotated_content_info': json.dumps(annotated_content_info),
'course_id': course_id, 'course_id': course_id,
'request': request, 'request': request,
'discussion_data': json.dumps({ discussion_id: [thread] }), 'discussion_data': json.dumps({ discussion_id: [utils.safe_content(thread)] }),
} }
return render_to_string('discussion/_single_thread.html', context) return render_to_string('discussion/_single_thread.html', context)
...@@ -194,7 +194,7 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -194,7 +194,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if request.is_ajax(): if request.is_ajax():
user_info = cc.User.from_django_user(request.user).safe_attributes() user_info = cc.User.from_django_user(request.user).to_dict()
thread = cc.Thread.find(thread_id).retrieve(recursive=True) thread = cc.Thread.find(thread_id).retrieve(recursive=True)
annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info) annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info)
context = {'thread': thread.to_dict(), 'course_id': course_id} context = {'thread': thread.to_dict(), 'course_id': course_id}
...@@ -202,7 +202,7 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -202,7 +202,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
return utils.JsonResponse({ return utils.JsonResponse({
'html': html, 'html': html,
'content': thread.to_dict(), 'content': utils.safe_content(thread.to_dict()),
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
}) })
...@@ -252,7 +252,7 @@ def user_profile(request, course_id, user_id): ...@@ -252,7 +252,7 @@ def user_profile(request, course_id, user_id):
if request.is_ajax(): if request.is_ajax():
return utils.JsonResponse({ return utils.JsonResponse({
'html': content, 'html': content,
'discussionData': threads, 'discussion_data': map(utils.safe_content, threads),
}) })
else: else:
context = { context = {
......
...@@ -164,6 +164,16 @@ class QueryCountDebugMiddleware(object): ...@@ -164,6 +164,16 @@ class QueryCountDebugMiddleware(object):
logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time)) logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time))
return response return response
def get_ability(course_id, content, user):
return {
'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"),
'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment"),
'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if content['type'] == 'comment' else False,
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment"),
'can_openclose': check_permissions_by_view(user, course_id, content, "openclose_thread") if content['type'] == 'thread' else False,
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
}
def get_annotated_content_info(course_id, content, user, user_info): def get_annotated_content_info(course_id, content, user, user_info):
voted = '' voted = ''
if content['id'] in user_info['upvoted_ids']: if content['id'] in user_info['upvoted_ids']:
...@@ -173,14 +183,7 @@ def get_annotated_content_info(course_id, content, user, user_info): ...@@ -173,14 +183,7 @@ def get_annotated_content_info(course_id, content, user, user_info):
return { return {
'voted': voted, 'voted': voted,
'subscribed': content['id'] in user_info['subscribed_thread_ids'], 'subscribed': content['id'] in user_info['subscribed_thread_ids'],
'ability': { 'ability': get_ability(course_id, content, user),
'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"),
'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment"),
'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if content['type'] == 'comment' else False,
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment"),
'can_openclose': check_permissions_by_view(user, course_id, content, "openclose_thread") if content['type'] == 'thread' else False,
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
},
} }
def get_annotated_content_infos(course_id, thread, user, user_info): def get_annotated_content_infos(course_id, thread, user, user_info):
...@@ -216,3 +219,17 @@ def extend_content(content): ...@@ -216,3 +219,17 @@ def extend_content(content):
'permalink': permalink(content), 'permalink': permalink(content),
} }
return merge_dict(content, content_info) return merge_dict(content, content_info)
def safe_content(content):
fields = [
'id', 'title', 'body', 'course_id', 'anonymous', 'endorsed',
'parent_id', 'thread_id', 'votes', 'closed',
'created_at', 'updated_at', 'depth', 'type',
'commentable_id', 'comments_count', 'at_position_list',
'children', 'highlighted_title', 'highlighted_body',
]
if content.get('anonymous') is False:
fields += ['username', 'user_id']
return strip_none(extract(content, fields))
...@@ -23,9 +23,16 @@ DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' ...@@ -23,9 +23,16 @@ DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
MITX_FEATURES['ENABLE_DISCUSSION'] = False MITX_FEATURES['ENABLE_DISCUSSION'] = False
MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
# IMPORTANT: With this enabled, the server must always be behind a proxy that
# strips the header HTTP_X_FORWARDED_PROTO from client requests. Otherwise,
# a user can fool our server into thinking it was an https connection.
# See https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
# for other warnings.
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
########################### NON-SECURE ENV CONFIG ############################## ########################### NON-SECURE ENV CONFIG ##############################
# Things like server locations, ports, etc. # Things like server locations, ports, etc.
with open(ENV_ROOT / "env.json") as env_file: with open(ENV_ROOT / "env.json") as env_file:
ENV_TOKENS = json.load(env_file) ENV_TOKENS = json.load(env_file)
...@@ -49,6 +56,8 @@ LOGGING = get_logger_config(LOG_DIR, ...@@ -49,6 +56,8 @@ LOGGING = get_logger_config(LOG_DIR,
COURSE_LISTINGS = ENV_TOKENS.get('COURSE_LISTINGS', {}) COURSE_LISTINGS = ENV_TOKENS.get('COURSE_LISTINGS', {})
SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {}) SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {})
COMMENTS_SERVICE_URL = ENV_TOKENS.get("COMMENTS_SERVICE_URL",'')
COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY",'')
############################## SECURE AUTH ITEMS ############################### ############################## SECURE AUTH ITEMS ###############################
# Secret things: passwords, access keys, etc. # Secret things: passwords, access keys, etc.
...@@ -67,5 +76,3 @@ XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE'] ...@@ -67,5 +76,3 @@ XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE']
if 'COURSE_ID' in ENV_TOKENS: if 'COURSE_ID' in ENV_TOKENS:
ASKBOT_URL = "courses/{0}/discussions/".format(ENV_TOKENS['COURSE_ID']) ASKBOT_URL = "courses/{0}/discussions/".format(ENV_TOKENS['COURSE_ID'])
COMMENTS_SERVICE_URL = ENV_TOKENS["COMMENTS_SERVICE_URL"]
COMMENTS_SERVICE_KEY = ENV_TOKENS["COMMENTS_SERVICE_KEY"]
...@@ -332,6 +332,8 @@ WIKI_CAN_CHANGE_PERMISSIONS = lambda article, user: user.is_staff or user.is_sup ...@@ -332,6 +332,8 @@ WIKI_CAN_CHANGE_PERMISSIONS = lambda article, user: user.is_staff or user.is_sup
WIKI_CAN_ASSIGN = lambda article, user: user.is_staff or user.is_superuser WIKI_CAN_ASSIGN = lambda article, user: user.is_staff or user.is_superuser
WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False
WIKI_LINK_LIVE_LOOKUPS = False
WIKI_LINK_DEFAULT_LEVEL = 2
################################# Jasmine ################################### ################################# Jasmine ###################################
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
......
...@@ -16,6 +16,8 @@ WIKI_ENABLED = False ...@@ -16,6 +16,8 @@ WIKI_ENABLED = False
MITX_FEATURES['ENABLE_TEXTBOOK'] = False MITX_FEATURES['ENABLE_TEXTBOOK'] = False
MITX_FEATURES['ENABLE_DISCUSSION'] = False MITX_FEATURES['ENABLE_DISCUSSION'] = False
MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = True # require that user be in the staff_* group to be able to enroll MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = True # require that user be in the staff_* group to be able to enroll
MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False
MITX_FEATURES['SUBDOMAIN_BRANDING'] = False
MITX_FEATURES['DISABLE_START_DATES'] = True MITX_FEATURES['DISABLE_START_DATES'] = True
# MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss # MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss
......
...@@ -33,16 +33,6 @@ class User(models.Model): ...@@ -33,16 +33,6 @@ class User(models.Model):
params = {'source_type': source.type, 'source_id': source.id} params = {'source_type': source.type, 'source_id': source.id}
response = perform_request('delete', _url_for_subscription(self.id), params) response = perform_request('delete', _url_for_subscription(self.id), params)
# TODO this is a hack to compensate for the fact that synchronization isn't
# happening properly.
def safe_attributes(self):
try:
return self.to_dict()
except:
self.save()
self._retrieve()
return self.to_dict()
def vote(self, voteable, value): def vote(self, voteable, value):
if voteable.type == 'thread': if voteable.type == 'thread':
url = _url_for_vote_thread(voteable.id) url = _url_for_vote_thread(voteable.id)
......
...@@ -90,7 +90,9 @@ if Backbone? ...@@ -90,7 +90,9 @@ if Backbone?
ability: (ability) -> ability: (ability) ->
for action, elemSelector of @model.actions for action, elemSelector of @model.actions
if not ability[action] if not ability[action]
@$(elemSelector).parent().remove() @$(elemSelector).parent().hide()
else
@$(elemSelector).parent().show()
$discussionContent: -> $discussionContent: ->
@_discussionContent ||= @$el.children(".discussion-content") @_discussionContent ||= @$el.children(".discussion-content")
...@@ -122,7 +124,7 @@ if Backbone? ...@@ -122,7 +124,7 @@ if Backbone?
url = @model.urlFor('retrieve') url = @model.urlFor('retrieve')
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
$loading: $(event.target) if event $loading: @$(".discussion-show-comments")
type: "GET" type: "GET"
url: url url: url
success: (response, textStatus) => success: (response, textStatus) =>
...@@ -191,6 +193,8 @@ if Backbone? ...@@ -191,6 +193,8 @@ if Backbone?
comment = @model.addComment response.content comment = @model.addComment response.content
commentView = new CommentView el: $comment[0], model: comment commentView = new CommentView el: $comment[0], model: comment
comment.updateInfo response.annotated_content_info comment.updateInfo response.annotated_content_info
if autowatch
@model.get('thread').set('subscribed', true)
@cancelReply() @cancelReply()
cancelReply: -> cancelReply: ->
...@@ -269,6 +273,7 @@ if Backbone? ...@@ -269,6 +273,7 @@ if Backbone?
data: data data: data
success: (response, textStatus) => success: (response, textStatus) =>
@model.set('closed', not closed) @model.set('closed', not closed)
@model.set('ability', response.ability)
edit: (event) -> edit: (event) ->
@$(".discussion-content-wrapper").hide() @$(".discussion-content-wrapper").hide()
...@@ -279,11 +284,11 @@ if Backbone? ...@@ -279,11 +284,11 @@ if Backbone?
view = {} view = {}
view.id = @model.id view.id = @model.id
if @model.get('type') == 'thread' if @model.get('type') == 'thread'
view.title = @$(".thread-raw-title").html() view.title = @model.get('title')
view.body = @$(".thread-raw-body").html() view.body = @model.get('body')
view.tags = @$(".thread-raw-tags").html() view.tags = @model.get('tags')
else else
view.body = @$(".comment-raw-body").html() view.body = @model.get('body')
@$discussionContent().append Mustache.render DiscussionUtil.getTemplate("_edit_#{@model.get('type')}"), view @$discussionContent().append Mustache.render DiscussionUtil.getTemplate("_edit_#{@model.get('type')}"), view
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "#{@model.get('type')}-body-edit" DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "#{@model.get('type')}-body-edit"
@$(".thread-tags-edit").tagsInput DiscussionUtil.tagsInputOptions() @$(".thread-tags-edit").tagsInput DiscussionUtil.tagsInputOptions()
...@@ -311,8 +316,12 @@ if Backbone? ...@@ -311,8 +316,12 @@ if Backbone?
success: (response, textStatus) => success: (response, textStatus) =>
DiscussionUtil.clearFormErrors @$(".discussion-update-errors") DiscussionUtil.clearFormErrors @$(".discussion-update-errors")
@$discussionContent().replaceWith(response.html) @$discussionContent().replaceWith(response.html)
@model.set response.content if @model.get('type') == 'thread'
@model.updateInfo response.annotated_content_info @model = new Thread response.content
else
@model = new Comment $.extend {}, response.content, { thread: @model.get('thread') }
@reconstruct()
@model.updateInfo response.annotated_content_info, { forceUpdate: true }
cancelEdit: (event) -> cancelEdit: (event) ->
@$(".discussion-content-edit").hide() @$(".discussion-content-edit").hide()
...@@ -330,8 +339,10 @@ if Backbone? ...@@ -330,8 +339,10 @@ if Backbone?
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
url: url url: url
type: "POST"
success: (response, textStatus) => success: (response, textStatus) =>
@$el.remove() @$el.remove()
if @model.get('type') == 'comment'
@model.get('thread').removeComment(@model) @model.get('thread').removeComment(@model)
events: events:
...@@ -382,6 +393,14 @@ if Backbone? ...@@ -382,6 +393,14 @@ if Backbone?
@initBody() @initBody()
@initCommentViews() @initCommentViews()
reconstruct: ->
@initBindings()
@initLocal()
@initTimeago()
@initTitle()
@initBody()
@delegateEvents()
class @Thread extends @Content class @Thread extends @Content
urlMappers: urlMappers:
'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id) 'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id)
......
...@@ -42,13 +42,17 @@ if Backbone? ...@@ -42,13 +42,17 @@ if Backbone?
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
$loading: $elem $loading: $elem
loadingCallback: ->
$(this).parent().append("<span class='discussion-loading'></span>")
loadedCallback: ->
$(this).parent().children(".discussion-loading").remove()
url: url url: url
type: "GET" type: "GET"
success: (response, textStatus) => success: (response, textStatus) =>
$parent = @$el.parent() $parent = @$el.parent()
@$el.replaceWith(response.html) @$el.replaceWith(response.html)
$discussion = $parent.find("section.discussion") $discussion = $parent.find("section.discussion")
@model.reset(response.discussionData, { silent: false }) @model.reset(response.discussion_data, { silent: false })
view = new DiscussionView el: $discussion[0], model: @model view = new DiscussionView el: $discussion[0], model: @model
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info) DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
$("html, body").animate({ scrollTop: 0 }, 0) $("html, body").animate({ scrollTop: 0 }, 0)
...@@ -109,6 +113,7 @@ if Backbone? ...@@ -109,6 +113,7 @@ if Backbone?
@$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @) @$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
@$el.children(".blank").hide()
@$(".new-post-form").show() @$(".new-post-form").show()
submitNewPost: (event) -> submitNewPost: (event) ->
...@@ -136,6 +141,8 @@ if Backbone? ...@@ -136,6 +141,8 @@ if Backbone?
$thread = $(response.html) $thread = $(response.html)
@$el.children(".threads").prepend($thread) @$el.children(".threads").prepend($thread)
@$el.children(".blank").remove()
@$(".new-post-similar-posts").empty() @$(".new-post-similar-posts").empty()
@$(".new-post-similar-posts-wrapper").hide() @$(".new-post-similar-posts-wrapper").hide()
@$(".new-post-title").val("").attr("prev-text", "") @$(".new-post-title").val("").attr("prev-text", "")
...@@ -154,6 +161,7 @@ if Backbone? ...@@ -154,6 +161,7 @@ if Backbone?
@$(".new-post-form").addClass("collapsed") @$(".new-post-form").addClass("collapsed")
else if @$el.hasClass("forum-discussion") else if @$el.hasClass("forum-discussion")
@$(".new-post-form").hide() @$(".new-post-form").hide()
@$el.children(".blank").show()
search: (event) -> search: (event) ->
event.preventDefault() event.preventDefault()
......
...@@ -27,7 +27,7 @@ if Backbone? ...@@ -27,7 +27,7 @@ if Backbone?
$discussion = @$el.find("section.discussion") $discussion = @$el.find("section.discussion")
$(event.target).html("Hide Discussion") $(event.target).html("Hide Discussion")
discussion = new Discussion() discussion = new Discussion()
discussion.reset(response.discussionData, {silent: false}) discussion.reset(response.discussion_data, {silent: false})
view = new DiscussionView(el: $discussion[0], model: discussion) view = new DiscussionView(el: $discussion[0], model: discussion)
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info) DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
@retrieved = true @retrieved = true
......
$ -> $ ->
$.fn.extend $.fn.extend
loading: -> loading: ->
$(this).after("<span class='discussion-loading'></span>") @$_loading = $("<span class='discussion-loading'></span>")
$(this).after(@$_loading)
loaded: -> loaded: ->
$(this).parent().children(".discussion-loading").remove() @$_loading.remove()
class @DiscussionUtil class @DiscussionUtil
...@@ -72,12 +73,16 @@ class @DiscussionUtil ...@@ -72,12 +73,16 @@ class @DiscussionUtil
params["beforeSend"] = -> params["beforeSend"] = ->
$elem.attr("disabled", "disabled") $elem.attr("disabled", "disabled")
if params["$loading"] if params["$loading"]
console.log "loading" if params["loadingCallback"]?
params["loadingCallback"].apply(params["$loading"])
else
params["$loading"].loading() params["$loading"].loading()
$.ajax(params).always -> $.ajax(params).always ->
$elem.removeAttr("disabled") $elem.removeAttr("disabled")
if params["$loading"] if params["$loading"]
console.log "loaded" if params["loadedCallback"]?
params["loadedCallback"].apply(params["$loading"])
else
params["$loading"].loaded() params["$loading"].loaded()
@get: ($elem, url, data, success) -> @get: ($elem, url, data, success) ->
......
...@@ -13,9 +13,10 @@ class @Navigation ...@@ -13,9 +13,10 @@ class @Navigation
active: active active: active
header: 'h3' header: 'h3'
autoHeight: false autoHeight: false
$('#accordion .ui-state-active').closest('.chapter').addClass('is-open')
$('#open_close_accordion a').click @toggle $('#open_close_accordion a').click @toggle
$('#accordion').show() $('#accordion').show()
$('#accordion a').click @setChapter
log: (event, ui) -> log: (event, ui) ->
log_event 'accordion', log_event 'accordion',
...@@ -24,3 +25,8 @@ class @Navigation ...@@ -24,3 +25,8 @@ class @Navigation
toggle: -> toggle: ->
$('.course-wrapper').toggleClass('closed') $('.course-wrapper').toggleClass('closed')
setChapter: ->
$('#accordion .is-open').removeClass('is-open')
$(this).closest('.chapter').addClass('is-open')
\ No newline at end of file
...@@ -4,13 +4,16 @@ ...@@ -4,13 +4,16 @@
var Gradebook = function($element) { var Gradebook = function($element) {
var _this = this; var _this = this;
var $element = $element; var $element = $element;
var $body = $('body');
var $grades = $element.find('.grades'); var $grades = $element.find('.grades');
var $studentTable = $element.find('.student-table');
var $gradeTable = $element.find('.grade-table'); var $gradeTable = $element.find('.grade-table');
var $search = $element.find('.student-search-field');
var $leftShadow = $('<div class="left-shadow"></div>'); var $leftShadow = $('<div class="left-shadow"></div>');
var $rightShadow = $('<div class="right-shadow"></div>'); var $rightShadow = $('<div class="right-shadow"></div>');
var tableHeight = $gradeTable.height(); var tableHeight = $gradeTable.height();
var maxScroll = $gradeTable.width() - $grades.width(); var maxScroll = $gradeTable.width() - $grades.width();
var $body = $('body');
var mouseOrigin; var mouseOrigin;
var tableOrigin; var tableOrigin;
...@@ -58,12 +61,35 @@ var Gradebook = function($element) { ...@@ -58,12 +61,35 @@ var Gradebook = function($element) {
var targetLeft = clamp($gradeTable.position().left, -maxScroll, 0); var targetLeft = clamp($gradeTable.position().left, -maxScroll, 0);
updateHorizontalPosition(targetLeft); updateHorizontalPosition(targetLeft);
setShadows(targetLeft); setShadows(targetLeft);
} };
var updateHorizontalPosition = function(left) { var updateHorizontalPosition = function(left) {
$gradeTable.css({ $gradeTable.css({
'left': left + 'px' 'left': left + 'px'
}); });
};
var highlightRow = function(e) {
$element.find('.highlight').removeClass('highlight');
var index = $(this).index();
$studentTable.find('tr').eq(index + 1).addClass('highlight');
$gradeTable.find('tr').eq(index + 1).addClass('highlight');
};
var filter = function(e) {
var term = $(this).val();
if(term.length > 0) {
$studentTable.find('tbody tr').hide();
$gradeTable.find('tbody tr').hide();
$studentTable.find('tbody tr:contains(' + term + ')').each(function(i) {
$(this).show();
$gradeTable.find('tr').eq($(this).index() + 1).show();
});
} else {
$studentTable.find('tbody tr').show();
$gradeTable.find('tbody tr').show();
}
} }
$leftShadow.css('height', tableHeight + 'px'); $leftShadow.css('height', tableHeight + 'px');
...@@ -72,5 +98,11 @@ var Gradebook = function($element) { ...@@ -72,5 +98,11 @@ var Gradebook = function($element) {
setShadows(0); setShadows(0);
$grades.css('height', tableHeight); $grades.css('height', tableHeight);
$gradeTable.bind('mousedown', startDrag); $gradeTable.bind('mousedown', startDrag);
$element.find('tr').bind('mouseover', highlightRow);
$search.bind('keyup', filter);
$(window).bind('resize', updateWidths); $(window).bind('resize', updateWidths);
} }
...@@ -27,11 +27,6 @@ var SequenceNav = function($element) { ...@@ -27,11 +27,6 @@ var SequenceNav = function($element) {
var offset = e.pageX - mouseOrigin; var offset = e.pageX - mouseOrigin;
var targetLeft = clamp(listOrigin + offset, -maxScroll, 0); var targetLeft = clamp(listOrigin + offset, -maxScroll, 0);
console.log('---------------');
console.log('offset: ' + offset);
console.log('target left: ' + targetLeft);
console.log('max: ' + maxScroll);
updateHorizontalPosition(targetLeft); updateHorizontalPosition(targetLeft);
setShadows(targetLeft); setShadows(targetLeft);
......
...@@ -150,10 +150,16 @@ $tag-text-color: #5b614f; ...@@ -150,10 +150,16 @@ $tag-text-color: #5b614f;
//user profile //user profile
.user-profile {
@extend .sidebar;
margin-top: 24px;
}
.sidebar-username { .sidebar-username {
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
line-height: 1.5em; line-height: 1.5em;
margin-top: 20px;
} }
.sidebar-user-roles { .sidebar-user-roles {
......
...@@ -133,18 +133,6 @@ div.gradebook-wrapper { ...@@ -133,18 +133,6 @@ div.gradebook-wrapper {
box-shadow: 0 1px 0 $table-border-color inset, 0 2px 0 rgba(255, 255, 255, .7) inset; box-shadow: 0 1px 0 $table-border-color inset, 0 2px 0 rgba(255, 255, 255, .7) inset;
border-left: 1px solid #ccc; border-left: 1px solid #ccc;
// &:before {
// content: '';
// display: block;
// position: absolute;
// left: 0;
// top: 0;
// z-index: 9999;
// width: 1px;
// height: 50px;
// @include linear-gradient(top, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, .15));
// }
&:first-child { &:first-child {
border-radius: 5px 0 0 0; border-radius: 5px 0 0 0;
box-shadow: 1px 1px 0 $table-border-color inset, 1px 2px 0 rgba(255, 255, 255, .7) inset; box-shadow: 1px 1px 0 $table-border-color inset, 1px 2px 0 rgba(255, 255, 255, .7) inset;
...@@ -205,6 +193,19 @@ div.gradebook-wrapper { ...@@ -205,6 +193,19 @@ div.gradebook-wrapper {
@extend .top-header; @extend .top-header;
} }
} }
.student-table tr:hover td,
.grade-table tr:hover td,
.student-table tr.highlight td,
.grade-table tr.highlight td {
border-color: #74b7d6;
@include linear-gradient(#8ed6f7, #76cbf4);
color: #333;
a {
color: #333;
}
}
} }
...@@ -13,6 +13,7 @@ div.course-wrapper { ...@@ -13,6 +13,7 @@ div.course-wrapper {
section.course-content { section.course-content {
@extend .content; @extend .content;
padding: 40px; padding: 40px;
line-height: 1.6;
h1 { h1 {
margin: 0 0 lh(); margin: 0 0 lh();
......
...@@ -75,6 +75,10 @@ section.course-index { ...@@ -75,6 +75,10 @@ section.course-index {
@include box-shadow(0 1px 0 #fff inset, 0 -1px 0 rgba(0, 0, 0, .1) inset); @include box-shadow(0 1px 0 #fff inset, 0 -1px 0 rgba(0, 0, 0, .1) inset);
@include transition(background-color .1s); @include transition(background-color .1s);
&.is-open {
background: #fff;
}
&:first-child { &:first-child {
border-radius: 3px 0 0 0; border-radius: 3px 0 0 0;
} }
......
...@@ -67,7 +67,39 @@ header.global.slim { ...@@ -67,7 +67,39 @@ header.global.slim {
@include linear-gradient(top, #fff, #eee); @include linear-gradient(top, #fff, #eee);
.guest .secondary { .guest .secondary {
margin-right: 0;
}
.guest .secondary a {
display: none; display: none;
&#login {
display: block;
@include background-image(linear-gradient(-90deg, lighten($blue, 8%), lighten($blue, 5%) 50%, $blue 50%, darken($blue, 10%) 100%));
border: 1px solid transparent;
border-color: darken($blue, 10%);
@include border-radius(3px);
@include box-sizing(border-box);
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
color: #fff;
display: inline-block;
font-family: $sans-serif;
font-size: 14px;
font-weight: bold;
@include inline-block;
letter-spacing: 0;
line-height: 1em;
margin: 4px;
padding: 6px 12px 8px;
text-decoration: none;
text-transform: none;
text-shadow: 0 -1px rgba(0, 0, 0, 0.6);
vertical-align: middle;
&:hover, &.active {
@include background-image(linear-gradient(-90deg, $blue, $blue 50%, $blue 50%, $blue 100%));
}
}
} }
nav { nav {
...@@ -86,7 +118,7 @@ header.global.slim { ...@@ -86,7 +118,7 @@ header.global.slim {
height: 40px; height: 40px;
position: absolute; position: absolute;
right: 3px; right: 3px;
top: -8px; top: 0;
width: 1px; width: 1px;
} }
...@@ -97,7 +129,7 @@ header.global.slim { ...@@ -97,7 +129,7 @@ header.global.slim {
height: 40px; height: 40px;
position: absolute; position: absolute;
right: 0px; right: 0px;
top: -12px; top: 0;
width: 1px; width: 1px;
} }
} }
...@@ -129,7 +161,7 @@ header.global.slim { ...@@ -129,7 +161,7 @@ header.global.slim {
a#signup { a#signup {
position: relative; position: relative;
margin-top: 4px; margin-top: 3px;
padding: 6px 12px 8px; padding: 6px 12px 8px;
text-transform: none; text-transform: none;
font-size: 14px; font-size: 14px;
......
...@@ -954,8 +954,7 @@ section.wiki { ...@@ -954,8 +954,7 @@ section.wiki {
.alert { .alert {
position: relative; position: relative;
top: -15px; margin: 24px 40px;
margin-bottom: 24px;
padding: 8px 12px; padding: 8px 12px;
border: 1px solid #EBE8BF; border: 1px solid #EBE8BF;
border-radius: 3px; border-radius: 3px;
...@@ -972,6 +971,10 @@ section.wiki { ...@@ -972,6 +971,10 @@ section.wiki {
} }
} }
.main-article .alert {
margin: 0 0 24px;
}
.missing { .missing {
max-width: 400px; max-width: 400px;
margin: lh(2) auto; margin: lh(2) auto;
......
...@@ -68,8 +68,8 @@ ...@@ -68,8 +68,8 @@
@include clearfix; @include clearfix;
border-bottom: 1px dotted rgb(220,220,220); border-bottom: 1px dotted rgb(220,220,220);
list-style: none; list-style: none;
margin-bottom: 20px; margin-bottom: 15px;
padding-bottom: 10px; padding-bottom: 17px;
&:hover { &:hover {
.title .icon { .title .icon {
...@@ -77,16 +77,20 @@ ...@@ -77,16 +77,20 @@
} }
} }
span {
display: block;
}
span.title { span.title {
color: $lighter-base-font-color; color: $lighter-base-font-color;
float: left;
font-family: $sans-serif; font-family: $sans-serif;
font-size: 13px;
.icon { .icon {
background-size: cover; background-size: cover;
float: left; float: left;
height: 19px; height: 19px;
margin: 2px 8px 0 0; margin: 0 6px 0 0;
opacity: 0.6; opacity: 0.6;
@include transition(all, 0.15s, linear); @include transition(all, 0.15s, linear);
width: 19px; width: 19px;
...@@ -112,7 +116,10 @@ ...@@ -112,7 +116,10 @@
span.data { span.data {
color: $lighter-base-font-color; color: $lighter-base-font-color;
font-weight: 700; font-weight: 700;
margin-left: 12px; margin-left: 26px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
} }
} }
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
</%def> </%def>
<%def name="render_content_with_comments(content, *args, **kwargs)"> <%def name="render_content_with_comments(content, *args, **kwargs)">
<div class="${content['type']}${helpers.show_if(' endorsed', content.get('endorsed'))}" _id="${content['id']}" _discussion_id="${content.get('commentable_id', '')}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous'))}"> <div class="${content['type'] | h}${helpers.show_if(' endorsed', content.get('endorsed')) | h}" _id="${content['id'] | h}" _discussion_id="${content.get('commentable_id', '') | h}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous')) | h}">
${render_content(content, *args, **kwargs)} ${render_content(content, *args, **kwargs)}
${render_comments(content.get('children', []), *args, **kwargs)} ${render_comments(content.get('children', []), *args, **kwargs)}
</div> </div>
......
<div class="discussion-module"> <div class="discussion-module">
<a class="discussion-show control-button" href="javascript:void(0)" discussion_id="${discussion_id}">Show Discussion</a> <a class="discussion-show control-button" href="javascript:void(0)" discussion_id="${discussion_id | h}">Show Discussion</a>
</div> </div>
<%namespace name="renderer" file="_content_renderer.html"/> <%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion forum-discussion" _id="${discussion_id}"> <section class="discussion forum-discussion" _id="${discussion_id | h}">
<div class="discussion-non-content local"> <div class="discussion-non-content local">
<div class="search-wrapper"> <div class="search-wrapper">
......
<%namespace name="renderer" file="_content_renderer.html"/> <%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion inline-discussion" _id="${discussion_id}"> <section class="discussion inline-discussion" _id="${discussion_id | h}">
<div class="discussion-non-content local"></div> <div class="discussion-non-content local"></div>
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%> %>
<%def name="link_to_page(_page, text)"> <%def name="link_to_page(_page, text)">
<a class="discussion-page-link" href="javascript:void(0)" page-url="${url_for_page(_page)}">${text}</a> <a class="discussion-page-link" href="javascript:void(0)" page-url="${url_for_page(_page) | h}">${text}</a>
</%def> </%def>
<%def name="div_page(_page)"> <%def name="div_page(_page)">
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
% endfor % endfor
</%def> </%def>
<div class="discussion-${discussion_type}-paginator discussion-paginator local"> <div class="discussion-${discussion_type | h}-paginator discussion-paginator local">
<div class="prev-page"> <div class="prev-page">
% if page > 1: % if page > 1:
${link_to_page(page - 1, "&lt; Previous page")} ${link_to_page(page - 1, "&lt; Previous page")}
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
</header> </header>
<ol class="discussion-sidebar-following-list"> <ol class="discussion-sidebar-following-list">
% for thread in recent_active_threads: % for thread in recent_active_threads:
<li><a href="${helpers.permalink(thread)}"><span class="sidebar-following-name">${thread['title']}</span> <span class="sidebar-vote-count">${thread['votes']['point']}</span></a></li> <li><a href="${helpers.permalink(thread) | h}"><span class="sidebar-following-name">${thread['title'] | h}</span> <span class="sidebar-vote-count">${thread['votes']['point'] | h}</span></a></li>
% endfor % endfor
<ol> <ol>
</article> </article>
......
...@@ -10,9 +10,9 @@ def base_url_for_search(): ...@@ -10,9 +10,9 @@ def base_url_for_search():
<form action="${base_url_for_search()}" method="get" class="discussion-search-form"> <form action="${base_url_for_search()}" method="get" class="discussion-search-form">
% if query_params.get('tags', None): % if query_params.get('tags', None):
<input class="search-input" type="text" value="[${tags}]${text}" id="keywords" autocomplete="off"/> <input class="search-input" type="text" value="[${tags | h}]${text | h}" id="keywords" autocomplete="off"/>
% else: % else:
<input class="search-input" type="text" value="${text}" id="keywords" autocomplete="off"/> <input class="search-input" type="text" value="${text | h}" id="keywords" autocomplete="off"/>
% endif % endif
<div class="discussion-link discussion-search-link" href="javascript:void(0)">Search posts</div> <div class="discussion-link discussion-search-link" href="javascript:void(0)">Search posts</div>
</form> </form>
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<a class="hide-similar-posts" href="javascript:void(0)">Hide</a> <a class="hide-similar-posts" href="javascript:void(0)">Hide</a>
<div class="new-post-similar-posts"> <div class="new-post-similar-posts">
% for thread in threads: % for thread in threads:
<a class="similar-post" href="${thread['permalink']}">${thread['title']}</a> <a class="similar-post" href="${thread['permalink'] | h}">${thread['title'] | h}</a>
% endfor % endfor
</div> </div>
% endif % endif
<%namespace name="renderer" file="_content_renderer.html"/> <%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion" _id="${discussion_id}"> <section class="discussion" _id="${discussion_id | h}">
<a class="discussion-title" href="javascript:void(0)">Discussion</a> <a class="discussion-title" href="javascript:void(0)">Discussion</a>
<div class="threads"> <div class="threads">
${renderer.render_content_with_comments(thread)} ${renderer.render_content_with_comments(thread)}
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
else: else:
return base_url + '?' + urlencode(merge(query_params, {'page': 1, 'sort_key': key, 'sort_order': order})) return base_url + '?' + urlencode(merge(query_params, {'page': 1, 'sort_key': key, 'sort_order': order}))
%> %>
<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 | h}" href="javascript:void(0)" sort-url="${url_for_sort(key, order) | h}">${title}</a>
</%def> </%def>
<div class="discussion-sort local"> <div class="discussion-sort local">
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
</header> </header>
<ol class="discussion-sidebar-tags-list"> <ol class="discussion-sidebar-tags-list">
% for tag, count in trending_tags: % for tag, count in trending_tags:
<li><a href="${helpers.url_for_tags(course.id, tag)}" class="thread-tag">${tag}</a><span class="sidebar-tag-count">&times;${count}</span></li> <li><a href="${helpers.url_for_tags(course.id, tag) | h}" class="thread-tag">${tag | h}</a><span class="sidebar-tag-count">&times;${count | h}</span></li>
% endfor % endfor
<ol> <ol>
</article> </article>
......
<%namespace name="renderer" file="_content_renderer.html"/> <%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion user-active-discussion" _id="${user_id}"> <section class="discussion user-active-discussion" _id="${user_id | h}">
<div class="discussion-non-content local"></div> <div class="discussion-non-content local"></div>
......
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
<% <%
role_names = sorted(map(attrgetter('name'), django_user.roles.all())) role_names = sorted(map(attrgetter('name'), django_user.roles.all()))
%> %>
<div class="sidebar-username">${django_user.username}</div> <div class="sidebar-username">${django_user.username | h}</div>
<div class="sidebar-user-roles"> <div class="sidebar-user-roles">
${", ".join(role_names)} ${", ".join(role_names)}
</div> </div>
<div class="sidebar-threads-count"><span>${profiled_user['threads_count']}</span> ${pluralize('discussion', profiled_user['threads_count'])} started</div> <div class="sidebar-threads-count"><span>${profiled_user['threads_count'] | h}</span> ${pluralize('discussion', profiled_user['threads_count']) | h} started</div>
<div class="sidebar-comments-count"><span>${profiled_user['comments_count']}</span> ${pluralize('comment', profiled_user['comments_count'])}</div> <div class="sidebar-comments-count"><span>${profiled_user['comments_count'] | h}</span> ${pluralize('comment', profiled_user['comments_count']) | h}</div>
% if check_permissions_by_view(user, course.id, content=None, name='update_moderator_status'): % if check_permissions_by_view(user, course.id, content=None, name='update_moderator_status'):
% if "Moderator" in role_names: % if "Moderator" in role_names:
<a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-revoke-moderator-button">Revoke Moderator provileges</a> <a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-revoke-moderator-button">Revoke Moderator provileges</a>
......
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%block name="bodyclass">discussion</%block> <%block name="bodyclass">discussion</%block>
<%block name="title"><title>Discussion – ${course.number}</title></%block> <%block name="title"><title>Discussion – ${course.number | h}</title></%block>
<%block name="headextra"> <%block name="headextra">
<%static:css group='course'/> <%static:css group='course'/>
......
...@@ -7,28 +7,25 @@ ...@@ -7,28 +7,25 @@
</div> </div>
<div class="discussion-right-wrapper"> <div class="discussion-right-wrapper">
<ul class="admin-actions"> <ul class="admin-actions">
<li><a href="javascript:void(0)" class="admin-endorse">Endorse</a></li> <li style="display: none;"><a href="javascript:void(0)" class="admin-endorse">Endorse</a></li>
<li><a href="javascript:void(0)" class="admin-edit">Edit</a></li> <li style="display: none;"><a href="javascript:void(0)" class="admin-edit">Edit</a></li>
<li><a href="javascript:void(0)" class="admin-delete">Delete</a></li> <li style="display: none;"><a href="javascript:void(0)" class="admin-delete">Delete</a></li>
{{#thread}} {{#thread}}
<li><a href="javascript:void(0)" class="admin-openclose">{{close_thread_text}}</a></li> <li style="display: none;"><a href="javascript:void(0)" class="admin-openclose">{{close_thread_text}}</a></li>
{{/thread}} {{/thread}}
</ul> </ul>
{{#thread}} {{#thread}}
<a class="thread-title" name="{{content.id}}" href="javascript:void(0)">{{content.displayed_title}}</a> <a class="thread-title" name="{{content.id}}" href="javascript:void(0)">{{content.displayed_title}}</a>
<div class="thread-raw-title" style="display: none">{{{content.title}}}</div>
{{/thread}} {{/thread}}
<div class="discussion-content-view"> <div class="discussion-content-view">
<a name="{{content.id}}" style="width: 0; height: 0; padding: 0; border: none;"></a> <a name="{{content.id}}" style="width: 0; height: 0; padding: 0; border: none;"></a>
<div class="content-body {{content.type}}-body" id="content-body-{{content.id}}">{{content.displayed_body}}</div> <div class="content-body {{content.type}}-body" id="content-body-{{content.id}}">{{content.displayed_body}}</div>
<div class="content-raw-body {{content.type}}-raw-body" style="display: none">{{{content.body}}}</div>
{{#thread}} {{#thread}}
<div class="thread-tags"> <div class="thread-tags">
{{#content.tags}} {{#content.tags}}
<a class="thread-tag" href="{{##url_for_tags}}{{.}}{{/url_for_tags}}">{{.}}</a> <a class="thread-tag" href="{{##url_for_tags}}{{.}}{{/url_for_tags}}">{{.}}</a>
{{/content.tags}} {{/content.tags}}
</div> </div>
<div class="thread-raw-tags" style="display: none">{{content.raw_tags}}</div>
{{/thread}} {{/thread}}
<div class="info"> <div class="info">
<div class="comment-time"> <div class="comment-time">
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%block name="bodyclass">discussion</%block> <%block name="bodyclass">discussion</%block>
<%block name="title"><title>Discussion – ${course.number}</title></%block> <%block name="title"><title>Discussion – ${course.number | h}</title></%block>
<%block name="headextra"> <%block name="headextra">
<%static:css group='course'/> <%static:css group='course'/>
...@@ -36,6 +36,6 @@ ...@@ -36,6 +36,6 @@
</section> </section>
<script type="text/javascript"> <script type="text/javascript">
var $$profiled_user_id = "${user.id | escapejs}"; var $$profiled_user_id = "${django_user.id | escapejs}";
var $$course_id = "${course.id | escapejs}"; var $$course_id = "${course.id | escapejs}";
</script> </script>
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% trans "Please go to the following page and choose a new password:" %} {% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %} {% block reset_link %}
https://www.edx.org{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %} https://{{domain}}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
{% endblock %} {% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }} {% trans "Your username, in case you've forgotten:" %} {{ user.username }}
......
...@@ -40,9 +40,6 @@ ...@@ -40,9 +40,6 @@
<script type="text/javascript"> <script type="text/javascript">
var sequenceNav; var sequenceNav;
$(document).ready(function() { $(document).ready(function() {
// console.log($('.sequence-nav'));
sequenceNav = new SequenceNav($('.sequence-nav')); sequenceNav = new SequenceNav($('.sequence-nav'));
console.log(sequenceNav);
}); });
</script> </script>
...@@ -33,11 +33,9 @@ ...@@ -33,11 +33,9 @@
</div> </div>
{% if urlpath %} {% if urlpath %}
<!--
<div class="see-children"> <div class="see-children">
<a href="{% url 'wiki:dir' path=urlpath.path %}">See all children</a> <a href="{% url 'wiki:dir' path=urlpath.path %}">See all children</a>
</div> </div>
-->
{% endif %} {% endif %}
</div> </div>
</div> </div>
......
...@@ -124,6 +124,8 @@ if settings.COURSEWARE_ENABLED: ...@@ -124,6 +124,8 @@ if settings.COURSEWARE_ENABLED:
'courseware.views.course_about', name="about_course"), 'courseware.views.course_about', name="about_course"),
#Inside the course #Inside the course
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/$',
'courseware.views.course_info', name="course_root"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$',
'courseware.views.course_info', name="info"), 'courseware.views.course_info', name="info"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/syllabus$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/syllabus$',
...@@ -197,7 +199,6 @@ if settings.QUICKEDIT: ...@@ -197,7 +199,6 @@ if settings.QUICKEDIT:
if settings.ASKBOT_ENABLED: if settings.ASKBOT_ENABLED:
urlpatterns += (url(r'^%s' % settings.ASKBOT_URL, include('askbot.urls')), \ urlpatterns += (url(r'^%s' % settings.ASKBOT_URL, include('askbot.urls')), \
url(r'^admin/', include(admin.site.urls)), \
url(r'^settings/', include('askbot.deps.livesettings.urls')), \ url(r'^settings/', include('askbot.deps.livesettings.urls')), \
url(r'^followit/', include('followit.urls')), \ url(r'^followit/', include('followit.urls')), \
# url(r'^robots.txt$', include('robots.urls')), # url(r'^robots.txt$', include('robots.urls')),
...@@ -206,8 +207,10 @@ if settings.ASKBOT_ENABLED: ...@@ -206,8 +207,10 @@ if settings.ASKBOT_ENABLED:
if settings.DEBUG: if settings.DEBUG:
## Jasmine ## Jasmine and admin
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),) urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),
url(r'^admin/', include(admin.site.urls)),
)
if settings.MITX_FEATURES.get('AUTH_USE_OPENID'): if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
urlpatterns += ( urlpatterns += (
......
-e git://github.com/MITx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles -e git://github.com/MITx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles
-e git://github.com/MITx/django-pipeline.git#egg=django-pipeline -e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
-e git://github.com/benjaoming/django-wiki.git@533c7fc#egg=django-wiki -e git://github.com/MITx/django-wiki.git@e2e84558#egg=django-wiki
-e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev -e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
-e common/lib/capa -e common/lib/capa
-e common/lib/xmodule -e common/lib/xmodule
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