From 7dd7de9566c8c7688319c401a6395be3cb1ab8ab Mon Sep 17 00:00:00 2001 From: ichuang Date: Fri, 17 Aug 2012 15:57:12 +0000 Subject: [PATCH 01/10] add gitreload to lms migration, so it doesn't have to run separately --- lms/djangoapps/lms_migration/migrate.py | 84 ++++++++++++++++++++++++- lms/urls.py | 2 + 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/lms_migration/migrate.py b/lms/djangoapps/lms_migration/migrate.py index dfdf86b4ac..6b9d01c81c 100644 --- a/lms/djangoapps/lms_migration/migrate.py +++ b/lms/djangoapps/lms_migration/migrate.py @@ -2,13 +2,21 @@ # migration tools for content team to go from stable-edx4edx to LMS+CMS # +import json import logging +import os 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 +import track.views + +try: + from django.views.decorators.csrf import csrf_exempt +except ImportError: + from django.contrib.csrf.middleware import csrf_exempt log = logging.getLogger("mitx.lms_migrate") LOCAL_DEBUG = True @@ -18,6 +26,15 @@ def escape(s): """escape HTML special characters in string""" return str(s).replace('<','<').replace('>','>') +def getip(request): + ''' + Extract IP address of requester from header, even if behind proxy + ''' + ip = request.META.get('HTTP_X_REAL_IP','') # nginx reverse proxy + if not ip: + ip = request.META.get('REMOTE_ADDR','None') + return ip + def manage_modulestores(request,reload_dir=None): ''' Manage the static in-memory modulestores. @@ -32,9 +49,7 @@ def manage_modulestores(request,reload_dir=None): #---------------------------------------- # 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') + ip = getip(request) if LOCAL_DEBUG: html += '

IP address: %s ' % ip @@ -108,3 +123,66 @@ def manage_modulestores(request,reload_dir=None): html += "" return HttpResponse(html) + +@csrf_exempt +def gitreload(request, reload_dir=None): + ''' + This can be used as a github WebHook Service Hook, for reloading of the content repo used by the LMS. + + If reload_dir is not None, then instruct the xml loader to reload that course directory. + ''' + html = "" + ip = getip(request) + + html += '

IP address: %s ' % ip + html += '

User: %s ' % request.user + + ALLOWED_IPS = ['207.97.227.253', '50.57.128.197', '108.171.174.178'] # hardcoded to github + if hasattr(settings,'ALLOWED_GITRELOAD_IPS'): # allow override in settings + ALLOWED_IPS = ALLOWED_GITRELOAD_IPS + + 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 += "" + log.debug('request denied from %s, ALLOWED_IPS=%s' % (ip,ALLOWED_IPS)) + return HttpResponse(html) + + #---------------------------------------- + # see if request is from github (POST with JSON) + + if reload_dir is None and 'payload' in request.POST: + payload = request.POST['payload'] + log.debug("payload=%s" % payload) + gitargs = json.loads(payload) + log.debug("gitargs=%s" % gitargs) + reload_dir = gitargs['repository']['name'] + log.debug("github reload_dir=%s" % reload_dir) + gdir = settings.DATA_DIR / reload_dir + if not os.path.exists(gdir): + log.debug("====> ERROR in gitreload - no such directory %s" % reload_dir) + return HttpResponse('Error') + cmd = "cd %s; git reset --hard HEAD; git clean -f -d; git pull origin; chmod g+w course.xml" % gdir + log.debug(os.popen(cmd).read()) + if hasattr(settings,'GITRELOAD_HOOK'): # hit this hook after reload, if set + gh = settings.GITRELOAD_HOOK + if gh: + ghurl = '%s/%s' % (gh,reload_dir) + r = requests.get(ghurl) + log.debug("GITRELOAD_HOOK to %s: %s" % (ghurl, r.text)) + + #---------------------------------------- + # reload course if specified + + if reload_dir is not None: + def_ms = modulestore() + if reload_dir not in def_ms.courses: + html += "

Error: '%s' is not a valid course directory

" % reload_dir + else: + html += "

Reloaded course directory '%s'

" % reload_dir + def_ms.try_load_course(reload_dir) + track.views.server_track(request, 'reloaded %s' % reload_dir, {}, page='migrate') + + return HttpResponse(html) diff --git a/lms/urls.py b/lms/urls.py index 89ef4babc4..1ed04d1a07 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -204,6 +204,8 @@ if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'): urlpatterns += ( url(r'^migrate/modules$', 'lms_migration.migrate.manage_modulestores'), url(r'^migrate/reload/(?P[^/]+)$', 'lms_migration.migrate.manage_modulestores'), + url(r'^gitreload$', 'lms_migration.migrate.gitreload'), + url(r'^gitreload/(?P[^/]+)$', 'lms_migration.migrate.gitreload'), ) if settings.MITX_FEATURES.get('ENABLE_SQL_TRACKING_LOGS'): From 4365b4a092faa5d90cb22022132bb41e61ebf09b Mon Sep 17 00:00:00 2001 From: ichuang Date: Fri, 17 Aug 2012 16:17:00 +0000 Subject: [PATCH 02/10] more flexible event logs retrieval --- common/djangoapps/track/views.py | 18 +++++++++++++++--- lms/urls.py | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/common/djangoapps/track/views.py b/common/djangoapps/track/views.py index b5f9c54665..910ed25d4b 100644 --- a/common/djangoapps/track/views.py +++ b/common/djangoapps/track/views.py @@ -84,15 +84,27 @@ def server_track(request, event_type, event, page=None): "time": datetime.datetime.utcnow().isoformat(), } - if event_type=="/event_logs" and request.user.is_staff: # don't log + if event_type.startswith("/event_logs") and request.user.is_staff: # don't log return log_event(event) @login_required @ensure_csrf_cookie -def view_tracking_log(request): +def view_tracking_log(request,args=''): if not request.user.is_staff: return redirect('/') - record_instances = TrackingLog.objects.all().order_by('-time')[0:100] + nlen = 100 + username = '' + if args: + for arg in args.split('/'): + if arg.isdigit(): + nlen = int(arg) + if arg.startswith('username='): + username = arg[9:] + + record_instances = TrackingLog.objects.all().order_by('-time') + if username: + record_instances = record_instances.filter(username=username) + record_instances = record_instances[0:nlen] return render_to_response('tracking_log.html',{'records':record_instances}) diff --git a/lms/urls.py b/lms/urls.py index 1ed04d1a07..776f08b49a 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -211,6 +211,7 @@ if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'): if settings.MITX_FEATURES.get('ENABLE_SQL_TRACKING_LOGS'): urlpatterns += ( url(r'^event_logs$', 'track.views.view_tracking_log'), + url(r'^event_logs/(?P.+)$', 'track.views.view_tracking_log'), ) urlpatterns = patterns(*urlpatterns) From a6c6d0f4d14836aa8bc528b5305eefc91f50a496 Mon Sep 17 00:00:00 2001 From: ichuang Date: Fri, 17 Aug 2012 14:49:59 -0400 Subject: [PATCH 03/10] MITX_FEATURES['USE_DJANGO_PIPELINE']=False in dev_ike by default --- lms/envs/dev_ike.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lms/envs/dev_ike.py b/lms/envs/dev_ike.py index 309ea1ac42..a9bd3ef97c 100644 --- a/lms/envs/dev_ike.py +++ b/lms/envs/dev_ike.py @@ -17,6 +17,9 @@ 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 +MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss + myhost = socket.gethostname() if ('edxvm' in myhost) or ('ocw' in myhost): MITX_FEATURES['DISABLE_LOGIN_BUTTON'] = True # auto-login with MIT certificate @@ -33,4 +36,9 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 fo INSTALLED_APPS = tuple([ app for app in INSTALLED_APPS if not app.startswith('debug_toolbar') ]) MIDDLEWARE_CLASSES = tuple([ mcl for mcl in MIDDLEWARE_CLASSES if not mcl.startswith('debug_toolbar') ]) -TEMPLATE_LOADERS = tuple([ app for app in TEMPLATE_LOADERS if not app.startswith('askbot') ]) +#TEMPLATE_LOADERS = tuple([ app for app in TEMPLATE_LOADERS if not app.startswith('askbot') ]) +#TEMPLATE_LOADERS = tuple([ app for app in TEMPLATE_LOADERS if not app.startswith('mitxmako') ]) +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + ) From f7d4831939dc70e8afe7a51e38e332042b7a5641 Mon Sep 17 00:00:00 2001 From: ichuang Date: Fri, 17 Aug 2012 15:20:19 -0400 Subject: [PATCH 04/10] update utility scripts for djang-1.4; will make them management cmds later --- utility-scripts/create_groups.py | 13 +++-- utility-scripts/create_user.py | 12 +++-- utility-scripts/manage_class_groups.py | 74 ++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 utility-scripts/manage_class_groups.py diff --git a/utility-scripts/create_groups.py b/utility-scripts/create_groups.py index 0e3245bb4d..8108498cb8 100644 --- a/utility-scripts/create_groups.py +++ b/utility-scripts/create_groups.py @@ -9,17 +9,20 @@ import os, sys, string, re sys.path.append(os.path.abspath('.')) os.environ['DJANGO_SETTINGS_MODULE'] = 'lms.envs.dev' -try: - from lms.envs.dev import * -except Exception as err: - print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." - sys.exit(-1) +#try: +# from lms.envs.dev import * +#except Exception as err: +# print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." +# sys.exit(-1) from django.conf import settings from django.contrib.auth.models import User, Group from path import path from lxml import etree +print "configured=",settings.configured +print settings.SETTINGS_MODULE + data_dir = settings.DATA_DIR print "data_dir = %s" % data_dir diff --git a/utility-scripts/create_user.py b/utility-scripts/create_user.py index 3ce9ce0ecf..5a52baff34 100644 --- a/utility-scripts/create_user.py +++ b/utility-scripts/create_user.py @@ -13,11 +13,13 @@ import readline sys.path.append(os.path.abspath('.')) os.environ['DJANGO_SETTINGS_MODULE'] = 'lms.envs.dev' -try: - from lms.envs.dev import * -except Exception as err: - print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." - sys.exit(-1) +#try: +# from lms.envs.dev import * +#except Exception as err: +# print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." +# sys.exit(-1) + +sys.path.append(os.path.abspath('common/djangoapps')) from student.models import UserProfile, Registration from external_auth.models import ExternalAuthMap diff --git a/utility-scripts/manage_class_groups.py b/utility-scripts/manage_class_groups.py new file mode 100644 index 0000000000..a21ec9151e --- /dev/null +++ b/utility-scripts/manage_class_groups.py @@ -0,0 +1,74 @@ +#!/usr/bin/python +# +# File: manage_class_groups +# +# list and edit membership in class staff group + +import os, sys, string, re +import datetime +from getpass import getpass +import json +import readline + +sys.path.append(os.path.abspath('.')) +os.environ['DJANGO_SETTINGS_MODULE'] = 'lms.envs.dev' + +#try: +# from lms.envs.dev import * +#except Exception as err: +# print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." +# sys.exit(-1) + +from django.conf import settings +from django.contrib.auth.models import User, Group + +#----------------------------------------------------------------------------- +# get all staff groups + +gset = Group.objects.all() + +print "Groups:" +for cnt,g in zip(range(len(gset)), gset): + print "%d. %s" % (cnt,g) + +gnum = int(raw_input('Choose group to manage (enter #): ')) + +group = gset[gnum] + +#----------------------------------------------------------------------------- +# users in group + +uall = User.objects.all() +print "----" +print "List of All Users: %s" % [str(x.username) for x in uall] +print "----" + +while True: + + print "Users in the group:" + + uset = group.user_set.all() + for cnt, u in zip(range(len(uset)), uset): + print "%d. %s" % (cnt, u) + + action = raw_input('Choose user to delete (enter #) or enter usernames (comma delim) to add: ') + + m = re.match('^[0-9]+$',action) + if m: + unum = int(action) + u = uset[unum] + print "Deleting user %s" % u + u.groups.remove(group) + + else: + for uname in action.split(','): + try: + user = User.objects.get(username=action) + except Exception as err: + print "Error %s" % err + continue + print "adding %s to group %s" % (user, group) + user.groups.add(group) + + + From b6e41dc05c56761acdd2e9c84b492e5027674de3 Mon Sep 17 00:00:00 2001 From: ichuang Date: Fri, 17 Aug 2012 15:46:09 -0400 Subject: [PATCH 05/10] fix view_tracking_log to show proper timezone --- common/djangoapps/track/views.py | 7 +++++++ lms/templates/tracking_log.html | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/track/views.py b/common/djangoapps/track/views.py index 910ed25d4b..434e75a63f 100644 --- a/common/djangoapps/track/views.py +++ b/common/djangoapps/track/views.py @@ -1,6 +1,7 @@ import json import logging import os +import pytz import datetime import dateutil.parser @@ -106,5 +107,11 @@ def view_tracking_log(request,args=''): if username: record_instances = record_instances.filter(username=username) record_instances = record_instances[0:nlen] + + # fix dtstamp + fmt = '%a %d-%b-%y %H:%M:%S' # "%Y-%m-%d %H:%M:%S %Z%z" + for rinst in record_instances: + rinst.dtstr = rinst.time.replace(tzinfo=pytz.utc).astimezone(pytz.timezone('US/Eastern')).strftime(fmt) + return render_to_response('tracking_log.html',{'records':record_instances}) diff --git a/lms/templates/tracking_log.html b/lms/templates/tracking_log.html index 66d375c2f3..24b249a583 100644 --- a/lms/templates/tracking_log.html +++ b/lms/templates/tracking_log.html @@ -3,7 +3,7 @@ % for rec in records: - + From b23bfc75bc33fbe0c3d589f8e8d259c5799a5ab2 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 19 Aug 2012 23:18:06 -0400 Subject: [PATCH 06/10] additional minor change: allow "inline" attrib on textline --- common/lib/capa/capa/inputtypes.py | 4 +++- common/lib/capa/capa/templates/textinput.html | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 8c513e7aec..9de0a52e15 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -293,7 +293,9 @@ def textline(element, value, status, render_template, msg=""): hidden = element.get('hidden', '') # if specified, then textline is hidden and id is stored in div of name given by hidden escapedict = {'"': '"'} value = saxutils.escape(value, escapedict) # otherwise, answers with quotes in them crashes the system! - context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size, 'msg': msg, 'hidden': hidden} + context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size, 'msg': msg, 'hidden': hidden, + 'inline': element.get('inline',''), + } html = render_template("textinput.html", context) try: xhtml = etree.XML(html) diff --git a/common/lib/capa/capa/templates/textinput.html b/common/lib/capa/capa/templates/textinput.html index 08aa8379a7..814517d11c 100644 --- a/common/lib/capa/capa/templates/textinput.html +++ b/common/lib/capa/capa/templates/textinput.html @@ -1,6 +1,14 @@ -
+
% if state == 'unsubmitted': -
+
% elif state == 'correct':
% elif state == 'incorrect': From 660c35f63eecf84fe329d26ec3493663494b7be4 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 20 Aug 2012 00:19:28 -0400 Subject: [PATCH 07/10] additional changes to make inline textinput work properly --- common/lib/capa/capa/templates/textinput.html | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/templates/textinput.html b/common/lib/capa/capa/templates/textinput.html index 814517d11c..63c0609107 100644 --- a/common/lib/capa/capa/templates/textinput.html +++ b/common/lib/capa/capa/templates/textinput.html @@ -10,11 +10,23 @@ % endif > % elif state == 'correct': -
+
% elif state == 'incorrect': -
+
% elif state == 'incomplete': -
+
% endif % if hidden:
From e3ecd22fa052fcce89b84d2cd8da662173317704 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 20 Aug 2012 18:38:05 -0400 Subject: [PATCH 08/10] github IPs in lms.envs.common; made create_user, create_groups, manage_course_groups management commands (in lms_migration) --- .../lms_migration/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/create_groups.py | 58 +++++++ .../management/commands/create_user.py | 146 +++++++++++++++++ lms/djangoapps/lms_migration/migrate.py | 4 +- lms/envs/common.py | 8 + lms/envs/dev.py | 2 + utility-scripts/create_groups.py | 49 ------ utility-scripts/create_user.py | 151 ------------------ utility-scripts/manage_class_groups.py | 74 --------- 10 files changed, 216 insertions(+), 276 deletions(-) create mode 100644 lms/djangoapps/lms_migration/management/__init__.py create mode 100644 lms/djangoapps/lms_migration/management/commands/__init__.py create mode 100644 lms/djangoapps/lms_migration/management/commands/create_groups.py create mode 100644 lms/djangoapps/lms_migration/management/commands/create_user.py delete mode 100644 utility-scripts/create_groups.py delete mode 100644 utility-scripts/create_user.py delete mode 100644 utility-scripts/manage_class_groups.py diff --git a/lms/djangoapps/lms_migration/management/__init__.py b/lms/djangoapps/lms_migration/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/lms_migration/management/commands/__init__.py b/lms/djangoapps/lms_migration/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/lms_migration/management/commands/create_groups.py b/lms/djangoapps/lms_migration/management/commands/create_groups.py new file mode 100644 index 0000000000..7b52795606 --- /dev/null +++ b/lms/djangoapps/lms_migration/management/commands/create_groups.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# +# File: create_groups.py +# +# Create all staff_* groups for classes in data directory. + +import os, sys, string, re + +from django.core.management.base import BaseCommand +from django.conf import settings +from django.contrib.auth.models import User, Group +from path import path +from lxml import etree + +def create_groups(): + ''' + Create staff and instructor groups for all classes in the data_dir + ''' + + data_dir = settings.DATA_DIR + print "data_dir = %s" % data_dir + + for course_dir in os.listdir(data_dir): + + if course_dir.startswith('.'): + continue + if not os.path.isdir(path(data_dir) / course_dir): + continue + + cxfn = path(data_dir) / course_dir / 'course.xml' + try: + coursexml = etree.parse(cxfn) + except Exception as err: + print "Oops, cannot read %s, skipping" % cxfn + continue + cxmlroot = coursexml.getroot() + course = cxmlroot.get('course') # TODO (vshnayder!!): read metadata from policy file(s) instead of from course.xml + if course is None: + print "oops, can't get course id for %s" % course_dir + continue + print "course=%s for course_dir=%s" % (course,course_dir) + + create_group('staff_%s' % course) # staff group + create_group('instructor_%s' % course) # instructor group (can manage staff group list) + +def create_group(gname): + if Group.objects.filter(name=gname): + print " group exists for %s" % gname + return + g = Group(name=gname) + g.save() + print " created group %s" % gname + +class Command(BaseCommand): + help = "Create groups associated with all courses in data_dir." + + def handle(self, *args, **options): + create_groups() diff --git a/lms/djangoapps/lms_migration/management/commands/create_user.py b/lms/djangoapps/lms_migration/management/commands/create_user.py new file mode 100644 index 0000000000..333608d467 --- /dev/null +++ b/lms/djangoapps/lms_migration/management/commands/create_user.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# +# File: create_user.py +# +# Create user. Prompt for groups and ExternalAuthMap + +import os, sys, string, re +import datetime +from getpass import getpass +import json +from random import choice +import readline + +from django.core.management.base import BaseCommand +from student.models import UserProfile, Registration +from external_auth.models import ExternalAuthMap +from django.contrib.auth.models import User, Group + +class MyCompleter(object): # Custom completer + + def __init__(self, options): + self.options = sorted(options) + + def complete(self, text, state): + if state == 0: # on first trigger, build possible matches + if text: # cache matches (entries that start with entered text) + self.matches = [s for s in self.options + if s and s.startswith(text)] + else: # no text entered, all matches possible + self.matches = self.options[:] + + # return match indexed by state + try: + return self.matches[state] + except IndexError: + return None + +def GenPasswd(length=8, chars=string.letters + string.digits): + return ''.join([choice(chars) for i in range(length)]) + +#----------------------------------------------------------------------------- +# main command + +class Command(BaseCommand): + help = "Create user, interactively; can add ExternalAuthMap for MIT user if email@MIT.EDU resolves properly." + + def handle(self, *args, **options): + + while True: + uname = raw_input('username: ') + if User.objects.filter(username=uname): + print "username %s already taken" % uname + else: + break + + make_eamap = False + if raw_input('Create MIT ExternalAuth? [n] ').lower()=='y': + email = '%s@MIT.EDU' % uname + if not email.endswith('@MIT.EDU'): + print "Failed - email must be @MIT.EDU" + sys.exit(-1) + mit_domain = 'ssl:MIT' + if ExternalAuthMap.objects.filter(external_id = email, external_domain = mit_domain): + print "Failed - email %s already exists as external_id" % email + sys.exit(-1) + make_eamap = True + password = GenPasswd(12) + + # get name from kerberos + kname = os.popen("finger %s | grep 'name:'" % email).read().strip().split('name: ')[1].strip() + name = raw_input('Full name: [%s] ' % kname).strip() + if name=='': + name = kname + print "name = %s" % name + else: + while True: + password = getpass() + password2 = getpass() + if password == password2: + break + print "Oops, passwords do not match, please retry" + + while True: + email = raw_input('email: ') + if User.objects.filter(email=email): + print "email %s already taken" % email + else: + break + + name = raw_input('Full name: ') + + + user = User(username=uname, email=email, is_active=True) + user.set_password(password) + try: + user.save() + except IntegrityError: + print "Oops, failed to create user %s, IntegrityError" % user + raise + + r = Registration() + r.register(user) + + up = UserProfile(user=user) + up.name = name + up.save() + + if make_eamap: + credentials = "/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN=%s/emailAddress=%s" % (name,email) + eamap = ExternalAuthMap(external_id = email, + external_email = email, + external_domain = mit_domain, + external_name = name, + internal_password = password, + external_credentials = json.dumps(credentials), + ) + eamap.user = user + eamap.dtsignup = datetime.datetime.now() + eamap.save() + + print "User %s created successfully!" % user + + if not raw_input('Add user %s to any groups? [n] ' % user).lower()=='y': + sys.exit(0) + + print "Here are the groups available:" + + groups = [str(g.name) for g in Group.objects.all()] + print groups + + completer = MyCompleter(groups) + readline.set_completer(completer.complete) + readline.parse_and_bind('tab: complete') + + while True: + gname = raw_input("Add group (tab to autocomplete, empty line to end): ") + if not gname: + break + if not gname in groups: + print "Unknown group %s" % gname + continue + g = Group.objects.get(name=gname) + user.groups.add(g) + print "Added %s to group %s" % (user,g) + + print "Done!" diff --git a/lms/djangoapps/lms_migration/migrate.py b/lms/djangoapps/lms_migration/migrate.py index 6b9d01c81c..a7d04a655d 100644 --- a/lms/djangoapps/lms_migration/migrate.py +++ b/lms/djangoapps/lms_migration/migrate.py @@ -63,7 +63,7 @@ def manage_modulestores(request,reload_dir=None): html += 'Permission denied' html += "" log.debug('request denied, ALLOWED_IPS=%s' % ALLOWED_IPS) - return HttpResponse(html) + return HttpResponse(html, status=403) #---------------------------------------- # reload course if specified @@ -137,7 +137,7 @@ def gitreload(request, reload_dir=None): html += '

IP address: %s ' % ip html += '

User: %s ' % request.user - ALLOWED_IPS = ['207.97.227.253', '50.57.128.197', '108.171.174.178'] # hardcoded to github + ALLOWED_IPS = [] # allow none by default if hasattr(settings,'ALLOWED_GITRELOAD_IPS'): # allow override in settings ALLOWED_IPS = ALLOWED_GITRELOAD_IPS diff --git a/lms/envs/common.py b/lms/envs/common.py index c412a3c8cd..265c500dbf 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -254,6 +254,14 @@ USE_L10N = True # Messages MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' +#################################### GITHUB ####################################### +# gitreload is used in LMS-workflow to pull content from github +# gitreload requests are only allowed from these IP addresses, which are +# the advertised public IPs of the github WebHook servers. +# These are listed, eg at https://github.com/MITx/mitx/admin/hooks + +ALLOWED_GITRELOAD = ['207.97.227.253', '50.57.128.197', '108.171.174.178'] + #################################### AWS ####################################### # S3BotoStorage insists on a timeout for uploaded assets. We should make it # permanent instead, but rather than trying to figure out exactly where that diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 6720c2050d..b269d293dd 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -73,6 +73,8 @@ 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 MITX_FEATURES['USE_XQA_SERVER'] = 'http://xqa:server@content-qa.mitx.mit.edu/xqa' +INSTALLED_APPS += ('lms_migration',) + LMS_MIGRATION_ALLOWED_IPS = ['127.0.0.1'] ################################ OpenID Auth ################################# diff --git a/utility-scripts/create_groups.py b/utility-scripts/create_groups.py deleted file mode 100644 index 8108498cb8..0000000000 --- a/utility-scripts/create_groups.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/python -# -# File: create_groups.py -# -# Create all staff_* groups for classes in data directory. - -import os, sys, string, re - -sys.path.append(os.path.abspath('.')) -os.environ['DJANGO_SETTINGS_MODULE'] = 'lms.envs.dev' - -#try: -# from lms.envs.dev import * -#except Exception as err: -# print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." -# sys.exit(-1) - -from django.conf import settings -from django.contrib.auth.models import User, Group -from path import path -from lxml import etree - -print "configured=",settings.configured -print settings.SETTINGS_MODULE - -data_dir = settings.DATA_DIR -print "data_dir = %s" % data_dir - -for course_dir in os.listdir(data_dir): - # print course_dir - if not os.path.isdir(path(data_dir) / course_dir): - continue - - cxfn = path(data_dir) / course_dir / 'course.xml' - coursexml = etree.parse(cxfn) - cxmlroot = coursexml.getroot() - course = cxmlroot.get('course') - if course is None: - print "oops, can't get course id for %s" % course_dir - continue - print "course=%s for course_dir=%s" % (course,course_dir) - - gname = 'staff_%s' % course - if Group.objects.filter(name=gname): - print "group exists for %s" % gname - continue - g = Group(name=gname) - g.save() - print "created group %s" % gname diff --git a/utility-scripts/create_user.py b/utility-scripts/create_user.py deleted file mode 100644 index 5a52baff34..0000000000 --- a/utility-scripts/create_user.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/python -# -# File: create_user.py -# -# Create user. Prompt for groups and ExternalAuthMap - -import os, sys, string, re -import datetime -from getpass import getpass -import json -import readline - -sys.path.append(os.path.abspath('.')) -os.environ['DJANGO_SETTINGS_MODULE'] = 'lms.envs.dev' - -#try: -# from lms.envs.dev import * -#except Exception as err: -# print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." -# sys.exit(-1) - -sys.path.append(os.path.abspath('common/djangoapps')) - -from student.models import UserProfile, Registration -from external_auth.models import ExternalAuthMap -from django.contrib.auth.models import User, Group -from random import choice - -class MyCompleter(object): # Custom completer - - def __init__(self, options): - self.options = sorted(options) - - def complete(self, text, state): - if state == 0: # on first trigger, build possible matches - if text: # cache matches (entries that start with entered text) - self.matches = [s for s in self.options - if s and s.startswith(text)] - else: # no text entered, all matches possible - self.matches = self.options[:] - - # return match indexed by state - try: - return self.matches[state] - except IndexError: - return None - -def GenPasswd(length=8, chars=string.letters + string.digits): - return ''.join([choice(chars) for i in range(length)]) - -#----------------------------------------------------------------------------- -# main - -while True: - uname = raw_input('username: ') - if User.objects.filter(username=uname): - print "username %s already taken" % uname - else: - break - -make_eamap = False -if raw_input('Create MIT ExternalAuth? [n] ').lower()=='y': - email = '%s@MIT.EDU' % uname - if not email.endswith('@MIT.EDU'): - print "Failed - email must be @MIT.EDU" - sys.exit(-1) - mit_domain = 'ssl:MIT' - if ExternalAuthMap.objects.filter(external_id = email, external_domain = mit_domain): - print "Failed - email %s already exists as external_id" % email - sys.exit(-1) - make_eamap = True - password = GenPasswd(12) - - # get name from kerberos - kname = os.popen("finger %s | grep 'name:'" % email).read().strip().split('name: ')[1].strip() - name = raw_input('Full name: [%s] ' % kname).strip() - if name=='': - name = kname - print "name = %s" % name -else: - while True: - password = getpass() - password2 = getpass() - if password == password2: - break - print "Oops, passwords do not match, please retry" - - while True: - email = raw_input('email: ') - if User.objects.filter(email=email): - print "email %s already taken" % email - else: - break - - name = raw_input('Full name: ') - - -user = User(username=uname, email=email, is_active=True) -user.set_password(password) -try: - user.save() -except IntegrityError: - print "Oops, failed to create user %s, IntegrityError" % user - raise - -r = Registration() -r.register(user) - -up = UserProfile(user=user) -up.name = name -up.save() - -if make_eamap: - credentials = "/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN=%s/emailAddress=%s" % (name,email) - eamap = ExternalAuthMap(external_id = email, - external_email = email, - external_domain = mit_domain, - external_name = name, - internal_password = password, - external_credentials = json.dumps(credentials), - ) - eamap.user = user - eamap.dtsignup = datetime.datetime.now() - eamap.save() - -print "User %s created successfully!" % user - -if not raw_input('Add user %s to any groups? [n] ' % user).lower()=='y': - sys.exit(0) - -print "Here are the groups available:" - -groups = [str(g.name) for g in Group.objects.all()] -print groups - -completer = MyCompleter(groups) -readline.set_completer(completer.complete) -readline.parse_and_bind('tab: complete') - -while True: - gname = raw_input("Add group (tab to autocomplete, empty line to end): ") - if not gname: - break - if not gname in groups: - print "Unknown group %s" % gname - continue - g = Group.objects.get(name=gname) - user.groups.add(g) - print "Added %s to group %s" % (user,g) - -print "Done!" diff --git a/utility-scripts/manage_class_groups.py b/utility-scripts/manage_class_groups.py deleted file mode 100644 index a21ec9151e..0000000000 --- a/utility-scripts/manage_class_groups.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python -# -# File: manage_class_groups -# -# list and edit membership in class staff group - -import os, sys, string, re -import datetime -from getpass import getpass -import json -import readline - -sys.path.append(os.path.abspath('.')) -os.environ['DJANGO_SETTINGS_MODULE'] = 'lms.envs.dev' - -#try: -# from lms.envs.dev import * -#except Exception as err: -# print "Run this script from the top-level mitx directory (mitx_all/mitx), not a subdirectory." -# sys.exit(-1) - -from django.conf import settings -from django.contrib.auth.models import User, Group - -#----------------------------------------------------------------------------- -# get all staff groups - -gset = Group.objects.all() - -print "Groups:" -for cnt,g in zip(range(len(gset)), gset): - print "%d. %s" % (cnt,g) - -gnum = int(raw_input('Choose group to manage (enter #): ')) - -group = gset[gnum] - -#----------------------------------------------------------------------------- -# users in group - -uall = User.objects.all() -print "----" -print "List of All Users: %s" % [str(x.username) for x in uall] -print "----" - -while True: - - print "Users in the group:" - - uset = group.user_set.all() - for cnt, u in zip(range(len(uset)), uset): - print "%d. %s" % (cnt, u) - - action = raw_input('Choose user to delete (enter #) or enter usernames (comma delim) to add: ') - - m = re.match('^[0-9]+$',action) - if m: - unum = int(action) - u = uset[unum] - print "Deleting user %s" % u - u.groups.remove(group) - - else: - for uname in action.split(','): - try: - user = User.objects.get(username=action) - except Exception as err: - print "Error %s" % err - continue - print "adding %s to group %s" % (user, group) - user.groups.add(group) - - - From e1c9e52fc5c11e9312347f8b152ceac13711fad5 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 20 Aug 2012 19:18:00 -0400 Subject: [PATCH 09/10] move textinput display:inline into capa display.scss file as class --- common/lib/capa/capa/inputtypes.py | 1 + common/lib/capa/capa/templates/textinput.html | 32 ++++--------------- .../lib/xmodule/xmodule/css/capa/display.scss | 4 +++ lms/envs/dev_ike.py | 4 ++- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 9de0a52e15..9cd8d7a322 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -149,6 +149,7 @@ def optioninput(element, value, status, render_template, msg=''): 'state': status, 'msg': msg, 'options': osetdict, + 'inline': element.get('inline',''), } html = render_template("optioninput.html", context) diff --git a/common/lib/capa/capa/templates/textinput.html b/common/lib/capa/capa/templates/textinput.html index 63c0609107..9b66654117 100644 --- a/common/lib/capa/capa/templates/textinput.html +++ b/common/lib/capa/capa/templates/textinput.html @@ -1,32 +1,14 @@ -
+<% doinline = "inline" if inline else "" %> + +
% if state == 'unsubmitted': -
+
% elif state == 'correct': -
+
% elif state == 'incorrect': -
+
% elif state == 'incomplete': -
+
% endif % if hidden:
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 666ca57800..af0dafaa73 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -49,6 +49,10 @@ section.problem { } } + .inline { + display: inline; + } + div { p { &.answer { diff --git a/lms/envs/dev_ike.py b/lms/envs/dev_ike.py index a9bd3ef97c..3ae141a037 100644 --- a/lms/envs/dev_ike.py +++ b/lms/envs/dev_ike.py @@ -18,16 +18,18 @@ 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 -MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss +# MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss myhost = socket.gethostname() if ('edxvm' in myhost) or ('ocw' in myhost): MITX_FEATURES['DISABLE_LOGIN_BUTTON'] = True # auto-login with MIT certificate MITX_FEATURES['USE_XQA_SERVER'] = 'https://qisx.mit.edu/xqa' # needs to be ssl or browser blocks it + MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss if ('domU' in myhost): EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] = 'ichuang@mitx.mit.edu' # nonempty string = address for all activation emails + MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy From 65ea54f7e2a07411e3f34a678458549fe6031f5b Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 20 Aug 2012 19:23:15 -0400 Subject: [PATCH 10/10] typo in common.py ALLOWED_GITRELOAD_IPS fixed --- lms/envs/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index 265c500dbf..b48dbf60ce 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -260,7 +260,7 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' # the advertised public IPs of the github WebHook servers. # These are listed, eg at https://github.com/MITx/mitx/admin/hooks -ALLOWED_GITRELOAD = ['207.97.227.253', '50.57.128.197', '108.171.174.178'] +ALLOWED_GITRELOAD_IPS = ['207.97.227.253', '50.57.128.197', '108.171.174.178'] #################################### AWS ####################################### # S3BotoStorage insists on a timeout for uploaded assets. We should make it

datetimeusernameipaddrsourcetype
${rec.time}${rec.dtstr} ${rec.username} ${rec.ip} ${rec.event_source}