Merge branch 'master' of github.com:dementrock/mitx into discussion3
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from collections import defaultdict
|
||||
from fs.errors import ResourceNotFoundError
|
||||
from functools import wraps
|
||||
import logging
|
||||
@@ -114,3 +115,57 @@ def get_course_info_section(course, section_key):
|
||||
return "! Info section missing !"
|
||||
|
||||
raise KeyError("Invalid about key " + str(section_key))
|
||||
|
||||
def course_staff_group_name(course):
|
||||
'''
|
||||
course should be either a CourseDescriptor instance, or a string (the .course entry of a Location)
|
||||
'''
|
||||
if isinstance(course,str):
|
||||
coursename = course
|
||||
else:
|
||||
coursename = course.metadata.get('data_dir','UnknownCourseName')
|
||||
if not coursename: # Fall 2012: not all course.xml have metadata correct yet
|
||||
coursename = course.metadata.get('course','')
|
||||
return 'staff_%s' % coursename
|
||||
|
||||
def has_staff_access_to_course(user,course):
|
||||
'''
|
||||
Returns True if the given user has staff access to the course.
|
||||
This means that user is in the staff_* group, or is an overall admin.
|
||||
'''
|
||||
if user is None or (not user.is_authenticated()) or course is None:
|
||||
return False
|
||||
if user.is_staff:
|
||||
return True
|
||||
user_groups = [x[1] for x in user.groups.values_list()] # note this is the Auth group, not UserTestGroup
|
||||
staff_group = course_staff_group_name(course)
|
||||
log.debug('course %s user %s groups %s' % (staff_group, user, user_groups))
|
||||
if staff_group in user_groups:
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_access_to_course(user,course):
|
||||
if course.metadata.get('ispublic'):
|
||||
return True
|
||||
return has_staff_access_to_course(user,course)
|
||||
|
||||
def get_courses_by_university(user):
|
||||
'''
|
||||
Returns dict of lists of courses available, keyed by course.org (ie university).
|
||||
Courses are sorted by course.number.
|
||||
|
||||
if ACCESS_REQUIRE_STAFF_FOR_COURSE then list only includes those accessible to user.
|
||||
'''
|
||||
# TODO: Clean up how 'error' is done.
|
||||
# filter out any courses that errored.
|
||||
courses = [c for c in modulestore().get_courses()
|
||||
if isinstance(c, CourseDescriptor)]
|
||||
courses = sorted(courses, key=lambda course: course.number)
|
||||
universities = defaultdict(list)
|
||||
for course in courses:
|
||||
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
|
||||
if not has_access_to_course(user,course):
|
||||
continue
|
||||
universities[course.org].append(course)
|
||||
return universities
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from capa.xqueue_interface import qinterface
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from models import StudentModule, StudentModuleCache
|
||||
from static_replace import replace_urls
|
||||
@@ -15,6 +16,8 @@ from xmodule.exceptions import NotFoundError
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from xmodule_modifiers import replace_static_urls, add_histogram, wrap_xmodule
|
||||
|
||||
from courseware.courses import has_staff_access_to_course
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
|
||||
@@ -155,6 +158,10 @@ def get_module(user, request, location, student_module_cache, position=None):
|
||||
# TODO: Queuename should be derived from 'course_settings.json' of each course
|
||||
xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course
|
||||
|
||||
xqueue = { 'interface': qinterface,
|
||||
'callback_url': xqueue_callback_url,
|
||||
'default_queuename': xqueue_default_queuename.replace(' ','_') }
|
||||
|
||||
def _get_module(location):
|
||||
(module, _, _, _) = get_module(user, request, location,
|
||||
student_module_cache, position)
|
||||
@@ -166,8 +173,7 @@ def get_module(user, request, location, student_module_cache, position=None):
|
||||
system = ModuleSystem(track_function=make_track_function(request),
|
||||
render_template=render_to_string,
|
||||
ajax_url=ajax_url,
|
||||
xqueue_callback_url=xqueue_callback_url,
|
||||
xqueue_default_queuename=xqueue_default_queuename.replace(' ','_'),
|
||||
xqueue=xqueue,
|
||||
# TODO (cpennington): Figure out how to share info between systems
|
||||
filestore=descriptor.system.resources_fs,
|
||||
get_module=_get_module,
|
||||
@@ -188,8 +194,9 @@ def get_module(user, request, location, student_module_cache, position=None):
|
||||
module.metadata['data_dir']
|
||||
)
|
||||
|
||||
if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF') and user.is_staff:
|
||||
module.get_html = add_histogram(module.get_html, module)
|
||||
if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
|
||||
if has_staff_access_to_course(user, module.location.course):
|
||||
module.get_html = add_histogram(module.get_html, module)
|
||||
|
||||
# If StudentModule for this instance wasn't already in the database,
|
||||
# and this isn't a guest user, create it.
|
||||
@@ -219,6 +226,9 @@ def get_module(user, request, location, student_module_cache, position=None):
|
||||
|
||||
@csrf_exempt
|
||||
def xqueue_callback(request, userid, id, dispatch):
|
||||
'''
|
||||
Entry point for graded results from the queueing system.
|
||||
'''
|
||||
# Parse xqueue response
|
||||
get = request.POST.copy()
|
||||
try:
|
||||
@@ -276,6 +286,12 @@ def modx_dispatch(request, dispatch=None, id=None):
|
||||
'''
|
||||
# ''' (fix emacs broken parsing)
|
||||
|
||||
# Check for submitted files
|
||||
p = request.POST.copy()
|
||||
if request.FILES:
|
||||
for inputfile_id in request.FILES.keys():
|
||||
p[inputfile_id] = request.FILES[inputfile_id]
|
||||
|
||||
student_module_cache = StudentModuleCache(request.user, modulestore().get_item(id))
|
||||
instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
|
||||
|
||||
@@ -287,7 +303,7 @@ def modx_dispatch(request, dispatch=None, id=None):
|
||||
|
||||
# Let the module handle the AJAX
|
||||
try:
|
||||
ajax_return = instance.handle_ajax(dispatch, request.POST)
|
||||
ajax_return = instance.handle_ajax(dispatch, p)
|
||||
except NotFoundError:
|
||||
log.exception("Module indicating to user that request doesn't exist")
|
||||
raise Http404
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from collections import defaultdict
|
||||
import json
|
||||
import logging
|
||||
import urllib
|
||||
@@ -37,7 +36,7 @@ from xmodule.course_module import CourseDescriptor
|
||||
from util.cache import cache, cache_if_anonymous
|
||||
from student.models import UserTestGroup, CourseEnrollment
|
||||
from courseware import grades
|
||||
from courseware.courses import check_course
|
||||
from courseware.courses import check_course, get_courses_by_university
|
||||
|
||||
|
||||
import comment_client
|
||||
@@ -72,19 +71,12 @@ def user_groups(user):
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def courses(request):
|
||||
# TODO: Clean up how 'error' is done.
|
||||
|
||||
# filter out any courses that errored.
|
||||
courses = [c for c in modulestore().get_courses()
|
||||
if isinstance(c, CourseDescriptor)]
|
||||
courses = sorted(courses, key=lambda course: course.number)
|
||||
universities = defaultdict(list)
|
||||
for course in courses:
|
||||
universities[course.org].append(course)
|
||||
|
||||
'''
|
||||
Render "find courses" page. The course selection work is done in courseware.courses.
|
||||
'''
|
||||
universities = get_courses_by_university(request.user)
|
||||
return render_to_response("courses.html", {'universities': universities})
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
def gradebook(request, course_id):
|
||||
if 'course_admin' not in user_groups(request.user):
|
||||
@@ -164,6 +156,7 @@ def render_accordion(request, course, chapter, section):
|
||||
return render_to_string('accordion.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
def index(request, course_id, chapter=None, section=None,
|
||||
@@ -186,6 +179,10 @@ def index(request, course_id, chapter=None, section=None,
|
||||
- HTTPresponse
|
||||
'''
|
||||
course = check_course(course_id)
|
||||
registered = registered_for_course(course, request.user)
|
||||
if not registered:
|
||||
log.debug('User %s tried to view course %s but is not enrolled' % (request.user,course.location.url()))
|
||||
return redirect(reverse('about_course', args=[course.id]))
|
||||
|
||||
try:
|
||||
context = {
|
||||
@@ -279,14 +276,19 @@ def course_info(request, course_id):
|
||||
|
||||
return render_to_response('info.html', {'course': course})
|
||||
|
||||
|
||||
def registered_for_course(course, user):
|
||||
'''Return CourseEnrollment if user is registered for course, else False'''
|
||||
if user is None:
|
||||
return False
|
||||
if user.is_authenticated():
|
||||
return CourseEnrollment.objects.filter(user=user, course_id=course.id).exists()
|
||||
else:
|
||||
return False
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def course_about(request, course_id):
|
||||
def registered_for_course(course, user):
|
||||
if user.is_authenticated():
|
||||
return CourseEnrollment.objects.filter(user=user, course_id=course.id).exists()
|
||||
else:
|
||||
return False
|
||||
course = check_course(course_id, course_must_be_open=False)
|
||||
registered = registered_for_course(course, request.user)
|
||||
return render_to_response('portal/course_about.html', {'course': course, 'registered': registered})
|
||||
@@ -301,7 +303,7 @@ def university_profile(request, org_id):
|
||||
raise Http404("University Profile not found for {0}".format(org_id))
|
||||
|
||||
# Only grab courses for this org...
|
||||
courses = [c for c in all_courses if c.org == org_id]
|
||||
courses = get_courses_by_university(request.user)[org_id]
|
||||
context = dict(courses=courses, org_id=org_id)
|
||||
template_file = "university_profile/{0}.html".format(org_id).lower()
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ import dateutil
|
||||
THREADS_PER_PAGE = 5
|
||||
PAGES_NEARBY_DELTA = 2
|
||||
|
||||
def _should_perform_search(request):
|
||||
return bool(request.GET.get('text', False) or \
|
||||
request.GET.get('tags', False))
|
||||
|
||||
|
||||
def render_accordion(request, course, discussion_id):
|
||||
|
||||
discussion_info = utils.get_categorized_discussion_info(request, course)
|
||||
@@ -56,6 +61,7 @@ def render_discussion(request, course_id, threads, discussion_id=None, \
|
||||
'user_info': comment_client.get_user_info(request.user.id, raw=True),
|
||||
'course_id': course_id,
|
||||
'request': request,
|
||||
'performed_search': _should_perform_search(request),
|
||||
'pages_nearby_delta': PAGES_NEARBY_DELTA,
|
||||
'discussion_type': discussion_type,
|
||||
'base_url': base_url,
|
||||
@@ -81,7 +87,7 @@ def get_threads(request, course_id, discussion_id):
|
||||
'tags': request.GET.get('tags', ''),
|
||||
}
|
||||
|
||||
if query_params['text'] or query_params['tags']: #TODO do tags search without sunspot
|
||||
if _should_perform_search(request):
|
||||
query_params['commentable_id'] = discussion_id
|
||||
threads, page, num_pages = comment_client.search_threads(course_id, recursive=False, query_params=utils.strip_none(query_params))
|
||||
else:
|
||||
|
||||
0
lms/djangoapps/lms_migration/__init__.py
Normal file
0
lms/djangoapps/lms_migration/__init__.py
Normal file
110
lms/djangoapps/lms_migration/migrate.py
Normal file
110
lms/djangoapps/lms_migration/migrate.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#
|
||||
# migration tools for content team to go from stable-edx4edx to LMS+CMS
|
||||
#
|
||||
|
||||
import logging
|
||||
from pprint import pprint
|
||||
import xmodule.modulestore.django as xmodule_django
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
|
||||
log = logging.getLogger("mitx.lms_migrate")
|
||||
LOCAL_DEBUG = True
|
||||
ALLOWED_IPS = settings.LMS_MIGRATION_ALLOWED_IPS
|
||||
|
||||
def escape(s):
|
||||
"""escape HTML special characters in string"""
|
||||
return str(s).replace('<','<').replace('>','>')
|
||||
|
||||
def manage_modulestores(request,reload_dir=None):
|
||||
'''
|
||||
Manage the static in-memory modulestores.
|
||||
|
||||
If reload_dir is not None, then instruct the xml loader to reload that course directory.
|
||||
'''
|
||||
html = "<html><body>"
|
||||
|
||||
def_ms = modulestore()
|
||||
courses = def_ms.get_courses()
|
||||
|
||||
#----------------------------------------
|
||||
# check on IP address of requester
|
||||
|
||||
ip = request.META.get('HTTP_X_REAL_IP','') # nginx reverse proxy
|
||||
if not ip:
|
||||
ip = request.META.get('REMOTE_ADDR','None')
|
||||
|
||||
if LOCAL_DEBUG:
|
||||
html += '<h3>IP address: %s ' % ip
|
||||
html += '<h3>User: %s ' % request.user
|
||||
log.debug('request from ip=%s, user=%s' % (ip,request.user))
|
||||
|
||||
if not (ip in ALLOWED_IPS or 'any' in ALLOWED_IPS):
|
||||
if request.user and request.user.is_staff:
|
||||
log.debug('request allowed because user=%s is staff' % request.user)
|
||||
else:
|
||||
html += 'Permission denied'
|
||||
html += "</body></html>"
|
||||
log.debug('request denied, ALLOWED_IPS=%s' % ALLOWED_IPS)
|
||||
return HttpResponse(html)
|
||||
|
||||
#----------------------------------------
|
||||
# reload course if specified
|
||||
|
||||
if reload_dir is not None:
|
||||
if reload_dir not in def_ms.courses:
|
||||
html += "<h2><font color='red'>Error: '%s' is not a valid course directory</font></h2>" % reload_dir
|
||||
else:
|
||||
html += "<h2><font color='blue'>Reloaded course directory '%s'</font></h2>" % reload_dir
|
||||
def_ms.try_load_course(reload_dir)
|
||||
|
||||
#----------------------------------------
|
||||
|
||||
html += '<h2>Courses loaded in the modulestore</h2>'
|
||||
html += '<ol>'
|
||||
for cdir, course in def_ms.courses.items():
|
||||
html += '<li><a href="%s/migrate/reload/%s">%s</a> (%s)</li>' % (settings.MITX_ROOT_URL,
|
||||
escape(cdir),
|
||||
escape(cdir),
|
||||
course.location.url())
|
||||
html += '</ol>'
|
||||
|
||||
#----------------------------------------
|
||||
|
||||
dumpfields = ['definition','location','metadata']
|
||||
|
||||
for cdir, course in def_ms.courses.items():
|
||||
html += '<hr width="100%"/>'
|
||||
html += '<h2>Course: %s (%s)</h2>' % (course.metadata['display_name'],cdir)
|
||||
|
||||
for field in dumpfields:
|
||||
data = getattr(course,field)
|
||||
html += '<h3>%s</h3>' % field
|
||||
if type(data)==dict:
|
||||
html += '<ul>'
|
||||
for k,v in data.items():
|
||||
html += '<li>%s:%s</li>' % (escape(k),escape(v))
|
||||
html += '</ul>'
|
||||
else:
|
||||
html += '<ul><li>%s</li></ul>' % escape(data)
|
||||
|
||||
|
||||
#----------------------------------------
|
||||
|
||||
html += '<hr width="100%"/>'
|
||||
html += "courses: <pre>%s</pre>" % escape(courses)
|
||||
|
||||
ms = xmodule_django._MODULESTORES
|
||||
html += "modules: <pre>%s</pre>" % escape(ms)
|
||||
html += "default modulestore: <pre>%s</pre>" % escape(unicode(def_ms))
|
||||
|
||||
#----------------------------------------
|
||||
|
||||
log.debug('_MODULESTORES=%s' % ms)
|
||||
log.debug('courses=%s' % courses)
|
||||
log.debug('def_ms=%s' % unicode(def_ms))
|
||||
|
||||
html += "</body></html>"
|
||||
return HttpResponse(html)
|
||||
@@ -49,6 +49,17 @@ MITX_FEATURES = {
|
||||
## DO NOT SET TO True IN THIS FILE
|
||||
## Doing so will cause all courses to be released on production
|
||||
'DISABLE_START_DATES': False, # When True, all courses will be active, regardless of start date
|
||||
|
||||
'ENABLE_TEXTBOOK' : True,
|
||||
'ENABLE_DISCUSSION' : True,
|
||||
|
||||
'ENABLE_SQL_TRACKING_LOGS': False,
|
||||
'ENABLE_LMS_MIGRATION': False,
|
||||
|
||||
# extrernal access methods
|
||||
'ACCESS_REQUIRE_STAFF_FOR_COURSE': False,
|
||||
'AUTH_USE_OPENID': False,
|
||||
'AUTH_USE_MIT_CERTIFICATES' : False,
|
||||
}
|
||||
|
||||
# Used for A/B testing
|
||||
|
||||
@@ -14,6 +14,7 @@ DEBUG = True
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
MITX_FEATURES['DISABLE_START_DATES'] = True
|
||||
MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
|
||||
|
||||
WIKI_ENABLED = True
|
||||
|
||||
@@ -58,6 +59,12 @@ CACHE_TIMEOUT = 0
|
||||
# Dummy secret key for dev
|
||||
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
|
||||
|
||||
################################ LMS Migration #################################
|
||||
MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True
|
||||
MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll
|
||||
|
||||
LMS_MIGRATION_ALLOWED_IPS = ['127.0.0.1']
|
||||
|
||||
################################ OpenID Auth #################################
|
||||
MITX_FEATURES['AUTH_USE_OPENID'] = True
|
||||
MITX_FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] = True
|
||||
|
||||
@@ -9,108 +9,10 @@ sessions. Assumes structure:
|
||||
"""
|
||||
from .common import *
|
||||
from .logsettings import get_logger_config
|
||||
from .dev import *
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = True
|
||||
WIKI_ENABLED = False
|
||||
MITX_FEATURES['ENABLE_TEXTBOOK'] = 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['DISABLE_START_DATES'] = True
|
||||
|
||||
WIKI_ENABLED = True
|
||||
|
||||
LOGGING = get_logger_config(ENV_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=True)
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ENV_ROOT / "db" / "mitx.db",
|
||||
}
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
# functioning cache -- it relies on caching to load its settings in places.
|
||||
# In staging/prod envs, the sessions also live here.
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': 'mitx_loc_mem_cache',
|
||||
'KEY_FUNCTION': 'util.memcache.safe_key',
|
||||
},
|
||||
|
||||
# The general cache is what you get if you use our util.cache. It's used for
|
||||
# things like caching the course.xml file for different A/B test groups.
|
||||
# We set it to be a DummyCache to force reloading of course.xml in dev.
|
||||
# In staging environments, we would grab VERSION from data uploaded by the
|
||||
# push process.
|
||||
'general': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
'KEY_PREFIX': 'general',
|
||||
'VERSION': 4,
|
||||
'KEY_FUNCTION': 'util.memcache.safe_key',
|
||||
}
|
||||
}
|
||||
|
||||
# Dummy secret key for dev
|
||||
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
|
||||
|
||||
################################ OpenID Auth #################################
|
||||
MITX_FEATURES['AUTH_USE_OPENID'] = True
|
||||
|
||||
INSTALLED_APPS += ('external_auth',)
|
||||
INSTALLED_APPS += ('django_openid_auth',)
|
||||
#INSTALLED_APPS += ('ssl_auth',)
|
||||
|
||||
#MIDDLEWARE_CLASSES += (
|
||||
# #'ssl_auth.ssl_auth.NginxProxyHeaderMiddleware', # ssl authentication behind nginx proxy
|
||||
# )
|
||||
|
||||
#AUTHENTICATION_BACKENDS = (
|
||||
# 'django_openid_auth.auth.OpenIDBackend',
|
||||
# 'django.contrib.auth.backends.ModelBackend',
|
||||
# )
|
||||
|
||||
OPENID_CREATE_USERS = False
|
||||
OPENID_UPDATE_DETAILS_FROM_SREG = True
|
||||
OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id'
|
||||
OPENID_USE_AS_ADMIN_LOGIN = False
|
||||
#import external_auth.views as edXauth
|
||||
#OPENID_RENDER_FAILURE = edXauth.edXauth_openid
|
||||
|
||||
################################ DEBUG TOOLBAR #################################
|
||||
INSTALLED_APPS += ('debug_toolbar',)
|
||||
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
|
||||
INTERNAL_IPS = ('127.0.0.1',)
|
||||
|
||||
DEBUG_TOOLBAR_PANELS = (
|
||||
'debug_toolbar.panels.version.VersionDebugPanel',
|
||||
'debug_toolbar.panels.timer.TimerDebugPanel',
|
||||
'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
|
||||
'debug_toolbar.panels.headers.HeaderDebugPanel',
|
||||
'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
|
||||
'debug_toolbar.panels.sql.SQLDebugPanel',
|
||||
'debug_toolbar.panels.signals.SignalDebugPanel',
|
||||
'debug_toolbar.panels.logger.LoggingPanel',
|
||||
|
||||
# Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and
|
||||
# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
|
||||
# hit twice). So you can uncomment when you need to diagnose performance
|
||||
# problems, but you shouldn't leave it on.
|
||||
# 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
|
||||
)
|
||||
|
||||
############################ FILE UPLOADS (ASKBOT) #############################
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
MEDIA_ROOT = ENV_ROOT / "uploads"
|
||||
MEDIA_URL = "/static/uploads/"
|
||||
STATICFILES_DIRS.append(("uploads", MEDIA_ROOT))
|
||||
FILE_UPLOAD_TEMP_DIR = ENV_ROOT / "uploads"
|
||||
FILE_UPLOAD_HANDLERS = (
|
||||
'django.core.files.uploadhandler.MemoryFileUploadHandler',
|
||||
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
|
||||
)
|
||||
|
||||
########################### PIPELINE #################################
|
||||
|
||||
PIPELINE_SASS_ARGUMENTS = '-r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
|
||||
|
||||
@@ -112,14 +112,16 @@ $ ->
|
||||
|
||||
if Markdown?
|
||||
|
||||
Markdown.getMathCompatibleConverter = ->
|
||||
Markdown.getMathCompatibleConverter = (postProcessor) ->
|
||||
postProcessor ||= ((text) -> text)
|
||||
converter = Markdown.getSanitizingConverter()
|
||||
processor = new MathJaxProcessor()
|
||||
converter.hooks.chain "preConversion", MathJaxProcessor.removeMathWrapper(processor)
|
||||
converter.hooks.chain "postConversion", MathJaxProcessor.replaceMathWrapper(processor)
|
||||
converter.hooks.chain "postConversion", (text) ->
|
||||
postProcessor(MathJaxProcessor.replaceMathWrapper(processor)(text))
|
||||
converter
|
||||
|
||||
Markdown.makeWmdEditor = (elem, appended_id, imageUploadUrl) ->
|
||||
Markdown.makeWmdEditor = (elem, appended_id, imageUploadUrl, postProcessor) ->
|
||||
$elem = $(elem)
|
||||
|
||||
if not $elem.length
|
||||
@@ -136,7 +138,7 @@ $ ->
|
||||
.append($("<div>").attr("id", "wmd-preview#{_append}").addClass("wmd-panel wmd-preview"))
|
||||
$elem.append($wmdPanel)
|
||||
|
||||
converter = Markdown.getMathCompatibleConverter()
|
||||
converter = Markdown.getMathCompatibleConverter(postProcessor)
|
||||
|
||||
ajaxFileUpload = (imageUploadUrl, input, startUploadHandler) ->
|
||||
$("#loading").ajaxStart(-> $(this).show()).ajaxComplete(-> $(this).hide())
|
||||
|
||||
@@ -126,7 +126,7 @@ initializeFollowThread = (thread) ->
|
||||
else
|
||||
view = {
|
||||
id: id
|
||||
title: $local(".thread-title").html()
|
||||
title: $local(".thread-raw-title").html()
|
||||
body: $local(".thread-raw-body").html()
|
||||
tags: $local(".thread-raw-tags").html()
|
||||
}
|
||||
@@ -283,8 +283,13 @@ initializeFollowThread = (thread) ->
|
||||
text.replace(/\<\;highlight\>\;/g, "<span class='search-highlight'>")
|
||||
.replace(/\<\;\/highlight\>\;/g, "</span>")
|
||||
|
||||
stripHighlight = (text, type) ->
|
||||
text.replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, "")
|
||||
.replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, "")
|
||||
|
||||
|
||||
stripLatexHighlight = (text) ->
|
||||
text
|
||||
Discussion.processEachMathAndCode text, stripHighlight
|
||||
|
||||
markdownWithHighlight = (text) ->
|
||||
converter = Markdown.getMathCompatibleConverter()
|
||||
@@ -303,10 +308,7 @@ initializeFollowThread = (thread) ->
|
||||
|
||||
$contentBody = $local(".content-body")
|
||||
|
||||
console.log "raw html:"
|
||||
console.log $contentBody.html()
|
||||
|
||||
$contentBody.html markdownWithHighlight $contentBody.html()
|
||||
$contentBody.html Discussion.postMathJaxProcessor markdownWithHighlight $contentBody.html()
|
||||
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")]
|
||||
id = $content.attr("_id")
|
||||
|
||||
@@ -95,12 +95,25 @@ wmdEditors = {}
|
||||
else
|
||||
success(response, textStatus, xhr)
|
||||
|
||||
postMathJaxProcessor: (text) ->
|
||||
RE_INLINEMATH = /^\$([^\$]*)\$/g
|
||||
RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g
|
||||
Discussion.processEachMathAndCode text, (s, type) ->
|
||||
if type == 'display'
|
||||
s.replace RE_DISPLAYMATH, ($0, $1) ->
|
||||
"\\[" + $1 + "\\]"
|
||||
else if type == 'inline'
|
||||
s.replace RE_INLINEMATH, ($0, $1) ->
|
||||
"\\(" + $1 + "\\)"
|
||||
else
|
||||
s
|
||||
|
||||
makeWmdEditor: ($content, $local, cls_identifier) ->
|
||||
elem = $local(".#{cls_identifier}")
|
||||
id = $content.attr("_id")
|
||||
appended_id = "-#{cls_identifier}-#{id}"
|
||||
imageUploadUrl = Discussion.urlFor('upload')
|
||||
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl
|
||||
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.postMathJaxProcessor
|
||||
wmdEditors["#{cls_identifier}-#{id}"] = editor
|
||||
editor
|
||||
|
||||
@@ -164,3 +177,55 @@ wmdEditors = {}
|
||||
unfollowLink()
|
||||
else
|
||||
followLink()
|
||||
|
||||
processEachMathAndCode: (text, processor) ->
|
||||
|
||||
codeArchive = []
|
||||
|
||||
RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m
|
||||
RE_INLINEMATH = /^([^\$]*?)\$([^\$]+?)\$(.*)$/m
|
||||
|
||||
ESCAPED_DOLLAR = '@@ESCAPED_D@@'
|
||||
ESCAPED_BACKSLASH = '@@ESCAPED_B@@'
|
||||
|
||||
processedText = ""
|
||||
|
||||
$div = $("<div>").html(text)
|
||||
|
||||
$div.find("code").each (index, code) ->
|
||||
codeArchive.push $(code).html()
|
||||
$(code).html(codeArchive.length - 1)
|
||||
|
||||
text = $div.html()
|
||||
text = text.replace /\\\$/g, ESCAPED_DOLLAR
|
||||
|
||||
while true
|
||||
if RE_INLINEMATH.test(text)
|
||||
text = text.replace RE_INLINEMATH, ($0, $1, $2, $3) ->
|
||||
processedText += $1 + processor("$" + $2 + "$", 'inline')
|
||||
$3
|
||||
else if RE_DISPLAYMATH.test(text)
|
||||
text = text.replace RE_DISPLAYMATH, ($0, $1, $2, $3) ->
|
||||
processedText += $1 + processor("$$" + $2 + "$$", 'display')
|
||||
$3
|
||||
else
|
||||
processedText += text
|
||||
break
|
||||
|
||||
text = processedText
|
||||
text = text.replace(new RegExp(ESCAPED_DOLLAR, 'g'), '\\$')
|
||||
|
||||
text = text.replace /\\\\\\\\/g, ESCAPED_BACKSLASH
|
||||
text = text.replace /\\begin\{([a-z]*\*?)\}([\s\S]*?)\\end\{\1\}/img, ($0, $1, $2) ->
|
||||
processor("\\begin{#{$1}}" + $2 + "\\end{#{$1}}")
|
||||
text = text.replace(new RegExp(ESCAPED_BACKSLASH, 'g'), '\\\\\\\\')
|
||||
|
||||
$div = $("<div>").html(text)
|
||||
cnt = 0
|
||||
$div.find("code").each (index, code) ->
|
||||
$(code).html(processor(codeArchive[cnt], 'code'))
|
||||
cnt += 1
|
||||
|
||||
text = $div.html()
|
||||
|
||||
text
|
||||
|
||||
@@ -8,11 +8,14 @@ class @MathJaxDelayRenderer
|
||||
elapsedTime: 0
|
||||
mathjaxDelay: 0
|
||||
mathjaxTimeout: undefined
|
||||
bufferId: "mathjax_delay_buffer"
|
||||
bufferId = "mathjax_delay_buffer"
|
||||
numBuffers = 0
|
||||
|
||||
constructor: (params) ->
|
||||
params = params || {}
|
||||
@maxDelay = params["maxDelay"] || @maxDelay
|
||||
@bufferId = params["bufferId"] || (bufferId + numBuffers)
|
||||
numBuffers += 1
|
||||
@$buffer = $("<div>").attr("id", @bufferId).css("display", "none").appendTo($("body"))
|
||||
|
||||
# render: (params) ->
|
||||
|
||||
@@ -14,11 +14,17 @@ def url_class(url):
|
||||
<li class="courseware"><a href="${reverse('courseware', args=[course.id])}" class="${url_class('courseware')}">Courseware</a></li>
|
||||
<li class="info"><a href="${reverse('info', args=[course.id])}" class="${url_class('info')}">Course Info</a></li>
|
||||
% if user.is_authenticated():
|
||||
% if settings.MITX_FEATURES.get('ENABLE_TEXTBOOK'):
|
||||
<li class="book"><a href="${reverse('book', args=[course.id])}" class="${url_class('book')}">Textbook</a></li>
|
||||
% endif
|
||||
% if settings.MITX_FEATURES.get('ENABLE_DISCUSSION'):
|
||||
<li class="discussion"><a href="${reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id, course.id.replace('/', '_').replace('.', '_')])}" class="${url_class('discussion')}">Discussion</a></li>
|
||||
<li class="news"><a href="${reverse('news', args=[course.id])}" class="${url_class('news')}">News</a></li>
|
||||
% endif
|
||||
% endif
|
||||
% if settings.WIKI_ENABLED:
|
||||
<li class="wiki"><a href="${reverse('wiki_root', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li>
|
||||
% endif
|
||||
% if user.is_authenticated():
|
||||
<li class="profile"><a href="${reverse('profile', args=[course.id])}" class="${url_class('profile')}">Profile</a></li>
|
||||
% endif
|
||||
|
||||
7
lms/templates/discussion/_blank_slate.html
Normal file
7
lms/templates/discussion/_blank_slate.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="blank-state">
|
||||
% if performed_search:
|
||||
There is no result matching your search parameters.
|
||||
% else:
|
||||
There is no post here yet. Be the first one to post!
|
||||
% endif
|
||||
</div>
|
||||
@@ -15,11 +15,16 @@
|
||||
<div class="discussion-new-post control-button" href="javascript:void(0)">New Post</div>
|
||||
</div>
|
||||
<%include file="_sort.html" />
|
||||
<div class="threads">
|
||||
% for thread in threads:
|
||||
${renderer.render_thread(course_id, thread, show_comments=False)}
|
||||
% endfor
|
||||
</div>
|
||||
% if len(threads) == 0:
|
||||
<%include file="_blank_slate.html" />
|
||||
<div class="threads"></div>
|
||||
% else:
|
||||
<div class="threads">
|
||||
% for thread in threads:
|
||||
${renderer.render_thread(course_id, thread, show_comments=False)}
|
||||
% endfor
|
||||
</div>
|
||||
% endif
|
||||
<%include file="_paginator.html" />
|
||||
</section>
|
||||
|
||||
|
||||
@@ -8,11 +8,16 @@
|
||||
<div class="discussion-new-post control-button" href="javascript:void(0)">New Post</div>
|
||||
<%include file="_sort.html" />
|
||||
</div>
|
||||
<div class="threads">
|
||||
% for thread in threads:
|
||||
${renderer.render_thread(course_id, thread, show_comments=False)}
|
||||
% endfor
|
||||
</div>
|
||||
% if len(threads) == 0:
|
||||
<%include file="_blank_slate.html" />
|
||||
<div class="threads"></div>
|
||||
% else:
|
||||
<div class="threads">
|
||||
% for thread in threads:
|
||||
${renderer.render_thread(course_id, thread, show_comments=False)}
|
||||
% endfor
|
||||
</div>
|
||||
% endif
|
||||
<%include file="_paginator.html" />
|
||||
</section>
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
Sort by:
|
||||
${link_to_sort('date', 'date')}
|
||||
|
|
||||
${link_to_sort('activity', 'activity')}
|
||||
|
|
||||
${link_to_sort('votes', 'votes')}
|
||||
|
|
||||
${link_to_sort('comments', 'comments')}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
% else:
|
||||
<a class="thread-title" name="${content['id']}" href="javascript:void(0)">${content['title'] | h}</a>
|
||||
% endif
|
||||
<div class="thread-raw-title" style="display: none">${content['title']}</div>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
$(document).delegate('#class_enroll_form', 'ajax:success', function(data, json, xhr) {
|
||||
if(json.success) {
|
||||
location.href="${reverse('dashboard')}";
|
||||
}else{
|
||||
$('#register_message).html("<p><font color='red'>" + json.error + "</font></p>")
|
||||
}
|
||||
});
|
||||
})(this)
|
||||
@@ -60,9 +62,24 @@
|
||||
<div class="main-cta">
|
||||
%if user.is_authenticated():
|
||||
%if registered:
|
||||
<%
|
||||
## TODO: move this logic into a view
|
||||
if course.has_started() or settings.MITX_FEATURES['DISABLE_START_DATES']:
|
||||
course_target = reverse('info', args=[course.id])
|
||||
else:
|
||||
course_target = reverse('about_course', args=[course.id])
|
||||
show_link = settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION')
|
||||
%>
|
||||
%if show_link:
|
||||
<a href="${course_target}">
|
||||
%endif
|
||||
<span class="register disabled">You are registered for this course (${course.number}).</span>
|
||||
%if show_link:
|
||||
</a>
|
||||
%endif
|
||||
%else:
|
||||
<a href="#" class="register">Register for ${course.number}</a>
|
||||
<div id="register_message"></div>
|
||||
%endif
|
||||
%else:
|
||||
<a href="#signup-modal" class="register" rel="leanModal" data-notice='You must Sign Up or <a href="#login-modal" rel="leanModal">Log In</a> to enroll.'>Register for ${course.number}</a>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
${module_content}
|
||||
<div class="staff_info">
|
||||
definition = ${definition | h}
|
||||
metadata = ${metadata | h}
|
||||
</div>
|
||||
%if edit_link:
|
||||
<div><a href="${edit_link}">Edit</a></div>
|
||||
% endif
|
||||
<div class="staff_info">
|
||||
definition = <pre>${definition | h}</pre>
|
||||
metadata = ${metadata | h}
|
||||
</div>
|
||||
%if render_histogram:
|
||||
<div id="histogram_${element_id}" class="histogram" data-histogram="${histogram}"></div>
|
||||
%endif
|
||||
|
||||
14
lms/templates/tracking_log.html
Normal file
14
lms/templates/tracking_log.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<h1>Tracking Log</h1>
|
||||
<table border="1"><tr><th>datetime</th><th>username</th><th>ipaddr</th><th>source</th><th>type</th></tr>
|
||||
% for rec in records:
|
||||
<tr>
|
||||
<td>${rec.time}</td>
|
||||
<td>${rec.username}</td>
|
||||
<td>${rec.ip}</td>
|
||||
<td>${rec.event_source}</td>
|
||||
<td>${rec.event_type}</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</table>
|
||||
</html>
|
||||
11
lms/urls.py
11
lms/urls.py
@@ -176,6 +176,17 @@ if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
|
||||
url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'),
|
||||
)
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
|
||||
urlpatterns += (
|
||||
url(r'^migrate/modules$', 'lms_migration.migrate.manage_modulestores'),
|
||||
url(r'^migrate/reload/(?P<reload_dir>[^/]+)$', 'lms_migration.migrate.manage_modulestores'),
|
||||
)
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_SQL_TRACKING_LOGS'):
|
||||
urlpatterns += (
|
||||
url(r'^event_logs$', 'track.views.view_tracking_log'),
|
||||
)
|
||||
|
||||
urlpatterns = patterns(*urlpatterns)
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
Reference in New Issue
Block a user