Commit 44939495 by Rocky Duan

Merge branch 'master' of github.com:MITx/mitx

Conflicts:
	lms/templates/profile.html
parents 6733972f 9f4df44b
...@@ -108,7 +108,7 @@ def edit_item(request): ...@@ -108,7 +108,7 @@ def edit_item(request):
'contents': item.get_html(), 'contents': item.get_html(),
'js_module': item.js_module_name, 'js_module': item.js_module_name,
'category': item.category, 'category': item.category,
'name': item.name, 'url_name': item.url_name,
'previews': get_module_previews(request, item), 'previews': get_module_previews(request, item),
}) })
......
<section id="unit-wrapper"> <section id="unit-wrapper">
<header> <header>
<section> <section>
<h1 class="editable">${name}</h1> <h1 class="editable">${url_name}</h1>
<p class="${category}"><a href="#">${category}</a></p> <p class="${category}"><a href="#">${category}</a></p>
</section> </section>
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
% for week in weeks: % for week in weeks:
<li class="week" data-id="${week.location.url()}"> <li class="week" data-id="${week.location.url()}">
<header> <header>
<h1><a href="#" class="week-edit">${week.name}</a></h1> <h1><a href="#" class="week-edit">${week.url_name}</a></h1>
<ul> <ul>
% if 'goals' in week.metadata: % if 'goals' in week.metadata:
% for goal in week.metadata['goals']: % for goal in week.metadata['goals']:
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
data-type="${module.js_module_name}" data-type="${module.js_module_name}"
data-preview-type="${module.module_class.js_module_name}"> data-preview-type="${module.module_class.js_module_name}">
<a href="#" class="module-edit">${module.name}</a> <a href="#" class="module-edit">${module.url_name}</a>
<a href="#" class="draggable">handle</a> <a href="#" class="draggable">handle</a>
</li> </li>
% endfor % endfor
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
<a href="#" class="module-edit" <a href="#" class="module-edit"
data-id="${child.location.url()}" data-id="${child.location.url()}"
data-type="${child.js_module_name}" data-type="${child.js_module_name}"
data-preview-type="${child.module_class.js_module_name}">${child.name}</a> data-preview-type="${child.module_class.js_module_name}">${child.url_name}</a>
<a href="#" class="draggable">handle</a> <a href="#" class="draggable">handle</a>
</li> </li>
%endfor %endfor
......
'''
django admin pages for courseware model
'''
from external_auth.models import *
from django.contrib import admin
admin.site.register(ExternalAuthMap)
"""
WE'RE USING MIGRATIONS!
If you make changes to this model, be sure to create an appropriate migration
file and check it in at the same time as your model changes. To do that,
1. Go to the mitx dir
2. django-admin.py schemamigration student --auto --settings=lms.envs.dev --pythonpath=. description_of_your_change
3. Add the migration file created in mitx/common/djangoapps/external_auth/migrations/
"""
from django.db import models
from django.contrib.auth.models import User
class ExternalAuthMap(models.Model):
class Meta:
unique_together = (('external_id', 'external_domain'), )
external_id = models.CharField(max_length=255, db_index=True)
external_domain = models.CharField(max_length=255, db_index=True)
external_credentials = models.TextField(blank=True) # JSON dictionary
external_email = models.CharField(max_length=255, db_index=True)
external_name = models.CharField(blank=True,max_length=255, db_index=True)
user = models.OneToOneField(User, unique=True, db_index=True, null=True)
internal_password = models.CharField(blank=True, max_length=31) # randomly generated
dtcreated = models.DateTimeField('creation date',auto_now_add=True)
dtsignup = models.DateTimeField('signup date',null=True) # set after signup
def __unicode__(self):
s = "[%s] = (%s / %s)" % (self.external_id, self.external_name, self.external_email)
return s
import json
import logging
import random
import re
import string
from external_auth.models import ExternalAuthMap
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.shortcuts import redirect
from django.template import RequestContext
from mitxmako.shortcuts import render_to_response, render_to_string
try:
from django.views.decorators.csrf import csrf_exempt
except ImportError:
from django.contrib.csrf.middleware import csrf_exempt
from django_future.csrf import ensure_csrf_cookie
from util.cache import cache_if_anonymous
from django_openid_auth import auth as openid_auth
from openid.consumer.consumer import (Consumer, SUCCESS, CANCEL, FAILURE)
import django_openid_auth.views as openid_views
import student.views as student_views
log = logging.getLogger("mitx.external_auth")
@csrf_exempt
def default_render_failure(request, message, status=403, template_name='extauth_failure.html', exception=None):
"""Render an Openid error page to the user."""
message = "In openid_failure " + message
log.debug(message)
data = render_to_string( template_name, dict(message=message, exception=exception))
return HttpResponse(data, status=status)
#-----------------------------------------------------------------------------
# Openid
def edXauth_generate_password(length=12, chars=string.letters + string.digits):
"""Generate internal password for externally authenticated user"""
return ''.join([random.choice(chars) for i in range(length)])
@csrf_exempt
def edXauth_openid_login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME, render_failure=None):
"""Complete the openid login process"""
redirect_to = request.REQUEST.get(redirect_field_name, '')
render_failure = render_failure or \
getattr(settings, 'OPENID_RENDER_FAILURE', None) or \
default_render_failure
openid_response = openid_views.parse_openid_response(request)
if not openid_response:
return render_failure(request, 'This is an OpenID relying party endpoint.')
if openid_response.status == SUCCESS:
external_id = openid_response.identity_url
oid_backend = openid_auth.OpenIDBackend()
details = oid_backend._extract_user_details(openid_response)
log.debug('openid success, details=%s' % details)
return edXauth_external_login_or_signup(request,
external_id,
"openid:%s" % settings.OPENID_SSO_SERVER_URL,
details,
details.get('email',''),
'%s %s' % (details.get('first_name',''),details.get('last_name',''))
)
return render_failure(request, 'Openid failure')
#-----------------------------------------------------------------------------
# generic external auth login or signup
def edXauth_external_login_or_signup(request, external_id, external_domain, credentials, email, fullname,
retfun=None):
# see if we have a map from this external_id to an edX username
try:
eamap = ExternalAuthMap.objects.get(external_id = external_id,
external_domain = external_domain,
)
log.debug('Found eamap=%s' % eamap)
except ExternalAuthMap.DoesNotExist:
# go render form for creating edX user
eamap = ExternalAuthMap(external_id = external_id,
external_domain = external_domain,
external_credentials = json.dumps(credentials),
)
eamap.external_email = email
eamap.external_name = fullname
eamap.internal_password = edXauth_generate_password()
log.debug('created eamap=%s' % eamap)
eamap.save()
internal_user = eamap.user
if internal_user is None:
log.debug('ExtAuth: no user for %s yet, doing signup' % eamap.external_email)
return edXauth_signup(request, eamap)
uname = internal_user.username
user = authenticate(username=uname, password=eamap.internal_password)
if user is None:
log.warning("External Auth Login failed for %s / %s" % (uname,eamap.internal_password))
return edXauth_signup(request, eamap)
if not user.is_active:
log.warning("External Auth: user %s is not active" % (uname))
# TODO: improve error page
return render_failure(request, 'Account not yet activated: please look for link in your email')
login(request, user)
request.session.set_expiry(0)
student_views.try_change_enrollment(request)
log.info("Login success - {0} ({1})".format(user.username, user.email))
if retfun is None:
return redirect('/')
return retfun()
#-----------------------------------------------------------------------------
# generic external auth signup
@ensure_csrf_cookie
@cache_if_anonymous
def edXauth_signup(request, eamap=None):
"""
Present form to complete for signup via external authentication.
Even though the user has external credentials, he/she still needs
to create an account on the edX system, and fill in the user
registration form.
eamap is an ExteralAuthMap object, specifying the external user
for which to complete the signup.
"""
if eamap is None:
pass
request.session['ExternalAuthMap'] = eamap # save this for use by student.views.create_account
context = {'has_extauth_info': True,
'show_signup_immediately' : True,
'extauth_email': eamap.external_email,
'extauth_username' : eamap.external_name.split(' ')[0],
'extauth_name': eamap.external_name,
}
log.debug('ExtAuth: doing signup for %s' % eamap.external_email)
return student_views.main_index(extra_context=context)
#-----------------------------------------------------------------------------
# MIT SSL
def ssl_dn_extract_info(dn):
'''
Extract username, email address (may be anyuser@anydomain.com) and full name
from the SSL DN string. Return (user,email,fullname) if successful, and None
otherwise.
'''
ss = re.search('/emailAddress=(.*)@([^/]+)', dn)
if ss:
user = ss.group(1)
email = "%s@%s" % (user, ss.group(2))
else:
return None
ss = re.search('/CN=([^/]+)/', dn)
if ss:
fullname = ss.group(1)
else:
return None
return (user, email, fullname)
@csrf_exempt
def edXauth_ssl_login(request):
"""
This is called by student.views.index when MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
Used for MIT user authentication. This presumes the web server (nginx) has been configured
to require specific client certificates.
If the incoming protocol is HTTPS (SSL) then authenticate via client certificate.
The certificate provides user email and fullname; this populates the ExternalAuthMap.
The user is nevertheless still asked to complete the edX signup.
Else continues on with student.views.main_index, and no authentication.
"""
certkey = "SSL_CLIENT_S_DN" # specify the request.META field to use
cert = request.META.get(certkey,'')
if not cert:
cert = request.META.get('HTTP_'+certkey,'')
if not cert:
try:
cert = request._req.subprocess_env.get(certkey,'') # try the direct apache2 SSL key
except Exception as err:
pass
if not cert:
# no certificate information - go onward to main index
return student_views.main_index()
(user, email, fullname) = ssl_dn_extract_info(cert)
return edXauth_external_login_or_signup(request,
external_id=email,
external_domain="ssl:MIT",
credentials=cert,
email=email,
fullname=fullname,
retfun = student_views.main_index)
...@@ -23,7 +23,6 @@ from django.http import HttpResponse, Http404 ...@@ -23,7 +23,6 @@ from django.http import HttpResponse, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
#from BeautifulSoup import BeautifulSoup
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from django.core.cache import cache from django.core.cache import cache
...@@ -61,6 +60,19 @@ def index(request): ...@@ -61,6 +60,19 @@ def index(request):
if settings.COURSEWARE_ENABLED and request.user.is_authenticated(): if settings.COURSEWARE_ENABLED and request.user.is_authenticated():
return redirect(reverse('dashboard')) return redirect(reverse('dashboard'))
if settings.MITX_FEATURES.get('AUTH_USE_MIT_CERTIFICATES'):
from external_auth.views import edXauth_ssl_login
return edXauth_ssl_login(request)
return main_index()
def main_index(extra_context = {}):
'''
Render the edX main page.
extra_context is used to allow immediate display of certain modal windows, eg signup,
as used by external_auth.
'''
feed_data = cache.get("students_index_rss_feed_data") feed_data = cache.get("students_index_rss_feed_data")
if feed_data == None: if feed_data == None:
if hasattr(settings, 'RSS_URL'): if hasattr(settings, 'RSS_URL'):
...@@ -81,8 +93,9 @@ def index(request): ...@@ -81,8 +93,9 @@ def index(request):
for course in courses: for course in courses:
universities[course.org].append(course) universities[course.org].append(course)
return render_to_response('index.html', {'universities': universities, 'entries': entries}) context = {'universities': universities, 'entries': entries}
context.update(extra_context)
return render_to_response('index.html', context)
def course_from_id(id): def course_from_id(id):
course_loc = CourseDescriptor.id_to_location(id) course_loc = CourseDescriptor.id_to_location(id)
...@@ -257,11 +270,26 @@ def change_setting(request): ...@@ -257,11 +270,26 @@ def change_setting(request):
@ensure_csrf_cookie @ensure_csrf_cookie
def create_account(request, post_override=None): def create_account(request, post_override=None):
''' JSON call to enroll in the course. ''' '''
JSON call to create new edX account.
Used by form in signup_modal.html, which is included into navigation.html
'''
js = {'success': False} js = {'success': False}
post_vars = post_override if post_override else request.POST post_vars = post_override if post_override else request.POST
# if doing signup for an external authorization, then get email, password, name from the eamap
# don't use the ones from the form, since the user could have hacked those
DoExternalAuth = 'ExternalAuthMap' in request.session
if DoExternalAuth:
eamap = request.session['ExternalAuthMap']
email = eamap.external_email
name = eamap.external_name
password = eamap.internal_password
post_vars = dict(post_vars.items())
post_vars.update(dict(email=email, name=name, password=password))
log.debug('extauth test: post_vars = %s' % post_vars)
# Confirm we have a properly formed request # Confirm we have a properly formed request
for a in ['username', 'email', 'password', 'name']: for a in ['username', 'email', 'password', 'name']:
if a not in post_vars: if a not in post_vars:
...@@ -356,8 +384,9 @@ def create_account(request, post_override=None): ...@@ -356,8 +384,9 @@ def create_account(request, post_override=None):
'key': r.activation_key, 'key': r.activation_key,
} }
# composes activation email
subject = render_to_string('emails/activation_email_subject.txt', d) subject = render_to_string('emails/activation_email_subject.txt', d)
# Email subject *must not* contain newlines # Email subject *must not* contain newlines
subject = ''.join(subject.splitlines()) subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt', d) message = render_to_string('emails/activation_email.txt', d)
...@@ -382,6 +411,17 @@ def create_account(request, post_override=None): ...@@ -382,6 +411,17 @@ def create_account(request, post_override=None):
try_change_enrollment(request) try_change_enrollment(request)
if DoExternalAuth:
eamap.user = login_user
eamap.dtsignup = datetime.datetime.now()
eamap.save()
log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'],eamap))
if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
log.debug('bypassing activation email')
login_user.is_active = True
login_user.save()
js = {'success': True} js = {'success': True}
return HttpResponse(json.dumps(js), mimetype="application/json") return HttpResponse(json.dumps(js), mimetype="application/json")
......
...@@ -288,20 +288,30 @@ class LoncapaProblem(object): ...@@ -288,20 +288,30 @@ class LoncapaProblem(object):
try: try:
ifp = self.system.filestore.open(file) # open using ModuleSystem OSFS filestore ifp = self.system.filestore.open(file) # open using ModuleSystem OSFS filestore
except Exception as err: except Exception as err:
log.error('Error %s in problem xml include: %s' % (err, etree.tostring(inc, pretty_print=True))) log.error('Error %s in problem xml include: %s' % (
log.error('Cannot find file %s in %s' % (file, self.system.filestore)) err, etree.tostring(inc, pretty_print=True)))
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error log.error('Cannot find file %s in %s' % (
file, self.system.filestore))
# if debugging, don't fail - just log error
# TODO (vshnayder): need real error handling, display to users
if not self.system.get('DEBUG'):
raise raise
else: continue else:
continue
try: try:
incxml = etree.XML(ifp.read()) # read in and convert to XML incxml = etree.XML(ifp.read()) # read in and convert to XML
except Exception as err: except Exception as err:
log.error('Error %s in problem xml include: %s' % (err, etree.tostring(inc, pretty_print=True))) log.error('Error %s in problem xml include: %s' % (
err, etree.tostring(inc, pretty_print=True)))
log.error('Cannot parse XML in %s' % (file)) log.error('Cannot parse XML in %s' % (file))
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error # if debugging, don't fail - just log error
# TODO (vshnayder): same as above
if not self.system.get('DEBUG'):
raise raise
else: continue else:
parent = inc.getparent() # insert new XML into tree in place of inlcude continue
# insert new XML into tree in place of inlcude
parent = inc.getparent()
parent.insert(parent.index(inc), incxml) parent.insert(parent.index(inc), incxml)
parent.remove(inc) parent.remove(inc)
log.debug('Included %s into %s' % (file, self.problem_id)) log.debug('Included %s into %s' % (file, self.problem_id))
......
...@@ -121,13 +121,13 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor): ...@@ -121,13 +121,13 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor):
# Not proper format. Write html to file, return an empty tag # Not proper format. Write html to file, return an empty tag
filepath = u'{category}/{name}.html'.format(category=self.category, filepath = u'{category}/{name}.html'.format(category=self.category,
name=self.name) name=self.url_name)
resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
with resource_fs.open(filepath, 'w') as file: with resource_fs.open(filepath, 'w') as file:
file.write(self.definition['data']) file.write(self.definition['data'])
elt = etree.Element('html') elt = etree.Element('html')
elt.set("filename", self.name) elt.set("filename", self.url_name)
return elt return elt
...@@ -87,6 +87,7 @@ def path_to_location(modulestore, location, course_name=None): ...@@ -87,6 +87,7 @@ def path_to_location(modulestore, location, course_name=None):
n = len(path) n = len(path)
course_id = CourseDescriptor.location_to_id(path[0]) course_id = CourseDescriptor.location_to_id(path[0])
# pull out the location names
chapter = path[1].name if n > 1 else None chapter = path[1].name if n > 1 else None
section = path[2].name if n > 2 else None section = path[2].name if n > 2 else None
......
...@@ -23,11 +23,12 @@ class VideoModule(XModule): ...@@ -23,11 +23,12 @@ class VideoModule(XModule):
css = {'scss': [resource_string(__name__, 'css/video/display.scss')]} css = {'scss': [resource_string(__name__, 'css/video/display.scss')]}
js_module_name = "Video" js_module_name = "Video"
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs): def __init__(self, system, location, definition,
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs) instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition,
instance_state, shared_state, **kwargs)
xmltree = etree.fromstring(self.definition['data']) xmltree = etree.fromstring(self.definition['data'])
self.youtube = xmltree.get('youtube') self.youtube = xmltree.get('youtube')
self.name = xmltree.get('name')
self.position = 0 self.position = 0
if instance_state is not None: if instance_state is not None:
...@@ -71,7 +72,7 @@ class VideoModule(XModule): ...@@ -71,7 +72,7 @@ class VideoModule(XModule):
'streams': self.video_list(), 'streams': self.video_list(),
'id': self.location.html_id(), 'id': self.location.html_id(),
'position': self.position, 'position': self.position,
'name': self.name, 'display_name': self.display_name,
# TODO (cpennington): This won't work when we move to data that isn't on the filesystem # TODO (cpennington): This won't work when we move to data that isn't on the filesystem
'data_dir': self.metadata['data_dir'], 'data_dir': self.metadata['data_dir'],
}) })
......
...@@ -191,11 +191,20 @@ class XModule(HTMLSnippet): ...@@ -191,11 +191,20 @@ class XModule(HTMLSnippet):
self.instance_state = instance_state self.instance_state = instance_state
self.shared_state = shared_state self.shared_state = shared_state
self.id = self.location.url() self.id = self.location.url()
self.name = self.location.name self.url_name = self.location.name
self.category = self.location.category self.category = self.location.category
self.metadata = kwargs.get('metadata', {}) self.metadata = kwargs.get('metadata', {})
self._loaded_children = None self._loaded_children = None
@property
def display_name(self):
'''
Return a display name for the module: use display_name if defined in
metadata, otherwise convert the url name.
'''
return self.metadata.get('display_name',
self.url_name.replace('_', ' '))
def get_children(self): def get_children(self):
''' '''
Return module instances for all the children of this module. Return module instances for all the children of this module.
...@@ -339,6 +348,8 @@ class XModuleDescriptor(Plugin, HTMLSnippet): ...@@ -339,6 +348,8 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
module module
display_name: The name to use for displaying this module to the display_name: The name to use for displaying this module to the
user user
url_name: The name to use for this module in urls and other places
where a unique name is needed.
format: The format of this module ('Homework', 'Lab', etc) format: The format of this module ('Homework', 'Lab', etc)
graded (bool): Whether this module is should be graded or not graded (bool): Whether this module is should be graded or not
start (string): The date for which this module will be available start (string): The date for which this module will be available
...@@ -353,7 +364,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet): ...@@ -353,7 +364,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
self.metadata = kwargs.get('metadata', {}) self.metadata = kwargs.get('metadata', {})
self.definition = definition if definition is not None else {} self.definition = definition if definition is not None else {}
self.location = Location(kwargs.get('location')) self.location = Location(kwargs.get('location'))
self.name = self.location.name self.url_name = self.location.name
self.category = self.location.category self.category = self.location.category
self.shared_state_key = kwargs.get('shared_state_key') self.shared_state_key = kwargs.get('shared_state_key')
...@@ -361,6 +372,15 @@ class XModuleDescriptor(Plugin, HTMLSnippet): ...@@ -361,6 +372,15 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
self._inherited_metadata = set() self._inherited_metadata = set()
@property @property
def display_name(self):
'''
Return a display name for the module: use display_name if defined in
metadata, otherwise convert the url name.
'''
return self.metadata.get('display_name',
self.url_name.replace('_', ' '))
@property
def own_metadata(self): def own_metadata(self):
""" """
Return the metadata that is not inherited, but was defined on this module. Return the metadata that is not inherited, but was defined on this module.
......
...@@ -225,7 +225,7 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -225,7 +225,7 @@ class XmlDescriptor(XModuleDescriptor):
# Write it to a file if necessary # Write it to a file if necessary
if self.split_to_file(xml_object): if self.split_to_file(xml_object):
# Put this object in its own file # Put this object in its own file
filepath = self.__class__._format_filepath(self.category, self.name) filepath = self.__class__._format_filepath(self.category, self.url_name)
resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
with resource_fs.open(filepath, 'w') as file: with resource_fs.open(filepath, 'w') as file:
file.write(etree.tostring(xml_object, pretty_print=True)) file.write(etree.tostring(xml_object, pretty_print=True))
...@@ -238,10 +238,10 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -238,10 +238,10 @@ class XmlDescriptor(XModuleDescriptor):
xml_object.tail = '' xml_object.tail = ''
xml_object.set('filename', self.name) xml_object.set('filename', self.url_name)
# Add the metadata # Add the metadata
xml_object.set('url_name', self.name) xml_object.set('url_name', self.url_name)
for attr in self.metadata_attributes: for attr in self.metadata_attributes:
attr_map = self.xml_attribute_map.get(attr, AttrMap(attr)) attr_map = self.xml_attribute_map.get(attr, AttrMap(attr))
metadata_key = attr_map.metadata_key metadata_key = attr_map.metadata_key
......
...@@ -83,7 +83,7 @@ def get_course_about_section(course, section_key): ...@@ -83,7 +83,7 @@ def get_course_about_section(course, section_key):
log.warning("Missing about section {key} in course {url}".format(key=section_key, url=course.location.url())) log.warning("Missing about section {key} in course {url}".format(key=section_key, url=course.location.url()))
return None return None
elif section_key == "title": elif section_key == "title":
return course.metadata.get('display_name', course.name) return course.metadata.get('display_name', course.url_name)
elif section_key == "university": elif section_key == "university":
return course.location.org return course.location.org
elif section_key == "number": elif section_key == "number":
......
...@@ -12,18 +12,23 @@ _log = logging.getLogger("mitx.courseware") ...@@ -12,18 +12,23 @@ _log = logging.getLogger("mitx.courseware")
def grade_sheet(student, course, grader, student_module_cache): def grade_sheet(student, course, grader, student_module_cache):
""" """
This pulls a summary of all problems in the course. It returns a dictionary with two datastructures: This pulls a summary of all problems in the course. It returns a dictionary
with two datastructures:
- courseware_summary is a summary of all sections with problems in the course. It is organized as an array of chapters, - courseware_summary is a summary of all sections with problems in the
each containing an array of sections, each containing an array of scores. This contains information for graded and ungraded course. It is organized as an array of chapters, each containing an array of
problems, and is good for displaying a course summary with due dates, etc. sections, each containing an array of scores. This contains information for
graded and ungraded problems, and is good for displaying a course summary
with due dates, etc.
- grade_summary is the output from the course grader. More information on the format is in the docstring for CourseGrader. - grade_summary is the output from the course grader. More information on
the format is in the docstring for CourseGrader.
Arguments: Arguments:
student: A User object for the student to grade student: A User object for the student to grade
course: An XModule containing the course to grade course: An XModule containing the course to grade
student_module_cache: A StudentModuleCache initialized with all instance_modules for the student student_module_cache: A StudentModuleCache initialized with all
instance_modules for the student
""" """
totaled_scores = {} totaled_scores = {}
chapters = [] chapters = []
...@@ -51,12 +56,16 @@ def grade_sheet(student, course, grader, student_module_cache): ...@@ -51,12 +56,16 @@ def grade_sheet(student, course, grader, student_module_cache):
correct = total correct = total
if not total > 0: if not total > 0:
#We simply cannot grade a problem that is 12/0, because we might need it as a percentage #We simply cannot grade a problem that is 12/0, because we
#might need it as a percentage
graded = False graded = False
scores.append(Score(correct, total, graded, module.metadata.get('display_name'))) scores.append(Score(correct, total, graded,
module.metadata.get('display_name')))
section_total, graded_total = graders.aggregate_scores(
scores, s.metadata.get('display_name'))
section_total, graded_total = graders.aggregate_scores(scores, s.metadata.get('display_name'))
#Add the graded total to totaled_scores #Add the graded total to totaled_scores
format = s.metadata.get('format', "") format = s.metadata.get('format', "")
if format and graded_total.possible > 0: if format and graded_total.possible > 0:
...@@ -65,7 +74,8 @@ def grade_sheet(student, course, grader, student_module_cache): ...@@ -65,7 +74,8 @@ def grade_sheet(student, course, grader, student_module_cache):
totaled_scores[format] = format_scores totaled_scores[format] = format_scores
sections.append({ sections.append({
'section': s.metadata.get('display_name'), 'display_name': s.display_name,
'url_name': s.url_name,
'scores': scores, 'scores': scores,
'section_total': section_total, 'section_total': section_total,
'format': format, 'format': format,
...@@ -73,8 +83,9 @@ def grade_sheet(student, course, grader, student_module_cache): ...@@ -73,8 +83,9 @@ def grade_sheet(student, course, grader, student_module_cache):
'graded': graded, 'graded': graded,
}) })
chapters.append({'course': course.metadata.get('display_name'), chapters.append({'course': course.display_name,
'chapter': c.metadata.get('display_name'), 'display_name': c.display_name,
'url_name': c.url_name,
'sections': sections}) 'sections': sections})
grade_summary = grader.grade(totaled_scores) grade_summary = grader.grade(totaled_scores)
......
...@@ -61,11 +61,7 @@ def import_with_checks(course_dir, verbose=True): ...@@ -61,11 +61,7 @@ def import_with_checks(course_dir, verbose=True):
course_dirs=course_dirs) course_dirs=course_dirs)
def str_of_err(tpl): def str_of_err(tpl):
(msg, exc_info) = tpl (msg, exc_str) = tpl
if exc_info is None:
return msg
exc_str = '\n'.join(traceback.format_exception(*exc_info))
return '{msg}\n{exc}'.format(msg=msg, exc=exc_str) return '{msg}\n{exc}'.format(msg=msg, exc=exc_str)
courses = modulestore.get_courses() courses = modulestore.get_courses()
...@@ -83,7 +79,7 @@ def import_with_checks(course_dir, verbose=True): ...@@ -83,7 +79,7 @@ def import_with_checks(course_dir, verbose=True):
print '\n' print '\n'
print "=" * 40 print "=" * 40
print 'ERRORs during import:' print 'ERRORs during import:'
print '\n'.join(map(str_of_err,errors)) print '\n'.join(map(str_of_err, errors))
print "=" * 40 print "=" * 40
print '\n' print '\n'
......
...@@ -35,10 +35,12 @@ def toc_for_course(user, request, course, active_chapter, active_section): ...@@ -35,10 +35,12 @@ def toc_for_course(user, request, course, active_chapter, active_section):
Create a table of contents from the module store Create a table of contents from the module store
Return format: Return format:
[ {'name': name, 'sections': SECTIONS, 'active': bool}, ... ] [ {'display_name': name, 'url_name': url_name,
'sections': SECTIONS, 'active': bool}, ... ]
where SECTIONS is a list where SECTIONS is a list
[ {'name': name, 'format': format, 'due': due, 'active' : bool}, ...] [ {'display_name': name, 'url_name': url_name,
'format': format, 'due': due, 'active' : bool}, ...]
active is set for the section and chapter corresponding to the passed active is set for the section and chapter corresponding to the passed
parameters. Everything else comes from the xml, or defaults to "". parameters. Everything else comes from the xml, or defaults to "".
...@@ -54,19 +56,21 @@ def toc_for_course(user, request, course, active_chapter, active_section): ...@@ -54,19 +56,21 @@ def toc_for_course(user, request, course, active_chapter, active_section):
sections = list() sections = list()
for section in chapter.get_display_items(): for section in chapter.get_display_items():
active = (chapter.metadata.get('display_name') == active_chapter and active = (chapter.display_name == active_chapter and
section.metadata.get('display_name') == active_section) section.display_name == active_section)
hide_from_toc = section.metadata.get('hide_from_toc', 'false').lower() == 'true' hide_from_toc = section.metadata.get('hide_from_toc', 'false').lower() == 'true'
if not hide_from_toc: if not hide_from_toc:
sections.append({'name': section.metadata.get('display_name'), sections.append({'display_name': section.display_name,
'url_name': section.url_name,
'format': section.metadata.get('format', ''), 'format': section.metadata.get('format', ''),
'due': section.metadata.get('due', ''), 'due': section.metadata.get('due', ''),
'active': active}) 'active': active})
chapters.append({'name': chapter.metadata.get('display_name'), chapters.append({'display_name': chapter.display_name,
'url_name': chapter.url_name,
'sections': sections, 'sections': sections,
'active': chapter.metadata.get('display_name') == active_chapter}) 'active': chapter.display_name == active_chapter})
return chapters return chapters
...@@ -76,8 +80,8 @@ def get_section(course_module, chapter, section): ...@@ -76,8 +80,8 @@ def get_section(course_module, chapter, section):
or None if this doesn't specify a valid section or None if this doesn't specify a valid section
course: Course url course: Course url
chapter: Chapter name chapter: Chapter url_name
section: Section name section: Section url_name
""" """
if course_module is None: if course_module is None:
...@@ -85,7 +89,7 @@ def get_section(course_module, chapter, section): ...@@ -85,7 +89,7 @@ def get_section(course_module, chapter, section):
chapter_module = None chapter_module = None
for _chapter in course_module.get_children(): for _chapter in course_module.get_children():
if _chapter.metadata.get('display_name') == chapter: if _chapter.url_name == chapter:
chapter_module = _chapter chapter_module = _chapter
break break
...@@ -94,7 +98,7 @@ def get_section(course_module, chapter, section): ...@@ -94,7 +98,7 @@ def get_section(course_module, chapter, section):
section_module = None section_module = None
for _section in chapter_module.get_children(): for _section in chapter_module.get_children():
if _section.metadata.get('display_name') == section: if _section.url_name == section:
section_module = _section section_module = _section
break break
...@@ -141,12 +145,12 @@ def get_module(user, request, location, student_module_cache, position=None): ...@@ -141,12 +145,12 @@ def get_module(user, request, location, student_module_cache, position=None):
# Setup system context for module instance # Setup system context for module instance
ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/' ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/'
# Fully qualified callback URL for external queueing system # Fully qualified callback URL for external queueing system
xqueue_callback_url = (request.build_absolute_uri('/') + settings.MITX_ROOT_URL + xqueue_callback_url = (request.build_absolute_uri('/') + settings.MITX_ROOT_URL +
'xqueue/' + str(user.id) + '/' + descriptor.location.url() + '/' + 'xqueue/' + str(user.id) + '/' + descriptor.location.url() + '/' +
'score_update') 'score_update')
# Default queuename is course-specific and is derived from the course that # Default queuename is course-specific and is derived from the course that
# contains the current module. # contains the current module.
# TODO: Queuename should be derived from 'course_settings.json' of each course # TODO: Queuename should be derived from 'course_settings.json' of each course
xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course
......
...@@ -68,11 +68,6 @@ def user_groups(user): ...@@ -68,11 +68,6 @@ def user_groups(user):
return group_names return group_names
def format_url_params(params):
return [urllib.quote(string.replace(' ', '_'))
if string is not None else None
for string in params]
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_if_anonymous @cache_if_anonymous
...@@ -138,7 +133,6 @@ def profile(request, course_id, student_id=None): ...@@ -138,7 +133,6 @@ def profile(request, course_id, student_id=None):
'language': user_info.language, 'language': user_info.language,
'email': student.email, 'email': student.email,
'course': course, 'course': course,
'format_url_params': format_url_params,
'csrf': csrf(request)['csrf_token'] 'csrf': csrf(request)['csrf_token']
} }
context.update(grades.grade_sheet(student, course_module, course.grader, student_module_cache)) context.update(grades.grade_sheet(student, course_module, course.grader, student_module_cache))
...@@ -152,9 +146,9 @@ def render_accordion(request, course, chapter, section): ...@@ -152,9 +146,9 @@ def render_accordion(request, course, chapter, section):
If chapter and section are '' or None, renders a default accordion. If chapter and section are '' or None, renders a default accordion.
Returns (initialization_javascript, content)''' Returns the html string'''
# TODO (cpennington): do the right thing with courses # grab the table of contents
toc = toc_for_course(request.user, request, course, chapter, section) toc = toc_for_course(request.user, request, course, chapter, section)
active_chapter = 1 active_chapter = 1
...@@ -166,7 +160,6 @@ def render_accordion(request, course, chapter, section): ...@@ -166,7 +160,6 @@ def render_accordion(request, course, chapter, section):
('toc', toc), ('toc', toc),
('course_name', course.title), ('course_name', course.title),
('course_id', course.id), ('course_id', course.id),
('format_url_params', format_url_params),
('csrf', csrf(request)['csrf_token'])] + template_imports.items()) ('csrf', csrf(request)['csrf_token'])] + template_imports.items())
return render_to_string('accordion.html', context) return render_to_string('accordion.html', context)
...@@ -183,9 +176,9 @@ def index(request, course_id, chapter=None, section=None, ...@@ -183,9 +176,9 @@ def index(request, course_id, chapter=None, section=None,
Arguments: Arguments:
- request : HTTP request - request : HTTP request
- course : coursename (str) - course_id : course id (str: ORG/course/URL_NAME)
- chapter : chapter name (str) - chapter : chapter url_name (str)
- section : section name (str) - section : section url_name (str)
- position : position in module, eg of <sequential> module (str) - position : position in module, eg of <sequential> module (str)
Returns: Returns:
...@@ -194,16 +187,6 @@ def index(request, course_id, chapter=None, section=None, ...@@ -194,16 +187,6 @@ def index(request, course_id, chapter=None, section=None,
''' '''
course = check_course(course_id) course = check_course(course_id)
def clean(s):
''' Fixes URLs -- we convert spaces to _ in URLs to prevent
funny encoding characters and keep the URLs readable. This undoes
that transformation.
'''
return s.replace('_', ' ') if s is not None else None
chapter = clean(chapter)
section = clean(section)
try: try:
context = { context = {
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
...@@ -216,8 +199,6 @@ def index(request, course_id, chapter=None, section=None, ...@@ -216,8 +199,6 @@ def index(request, course_id, chapter=None, section=None,
look_for_module = chapter is not None and section is not None look_for_module = chapter is not None and section is not None
if look_for_module: if look_for_module:
# TODO (cpennington): Pass the right course in here
section_descriptor = get_section(course, chapter, section) section_descriptor = get_section(course, chapter, section)
if section_descriptor is not None: if section_descriptor is not None:
student_module_cache = StudentModuleCache(request.user, student_module_cache = StudentModuleCache(request.user,
......
...@@ -58,6 +58,21 @@ CACHE_TIMEOUT = 0 ...@@ -58,6 +58,21 @@ CACHE_TIMEOUT = 0
# Dummy secret key for dev # Dummy secret key for dev
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
################################ OpenID Auth #################################
MITX_FEATURES['AUTH_USE_OPENID'] = True
MITX_FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] = True
INSTALLED_APPS += ('external_auth',)
INSTALLED_APPS += ('django_openid_auth',)
OPENID_CREATE_USERS = False
OPENID_UPDATE_DETAILS_FROM_SREG = True
OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id' # TODO: accept more endpoints
OPENID_USE_AS_ADMIN_LOGIN = False
################################ MIT Certificates SSL Auth #################################
MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
################################ DEBUG TOOLBAR ################################# ################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar',) INSTALLED_APPS += ('debug_toolbar',)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
......
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%def name="make_chapter(chapter)"> <%def name="make_chapter(chapter)">
<h3><a href="#">${chapter['name']}</a></h3> <h3><a href="#">${chapter['display_name']}</a></h3>
<ul> <ul>
% for section in chapter['sections']: % for section in chapter['sections']:
<li${' class="active"' if 'active' in section and section['active'] else ''}> <li${' class="active"' if 'active' in section and section['active'] else ''}>
<a href="${reverse('courseware_section', args=[course_id] + format_url_params([chapter['name'], section['name']]))}"> <a href="${reverse('courseware_section', args=[course_id, chapter['url_name'], section['url_name']])}">
<p>${section['name']} <p>${section['display_name']}
<span class="subtitle"> <span class="subtitle">
${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''} ${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''}
</span> </span>
......
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>OpenID failed</title>
</head>
<body>
<h1>OpenID failed</h1>
<p>${message}</p>
</body>
</html>
...@@ -144,3 +144,31 @@ ...@@ -144,3 +144,31 @@
<iframe width="640" height="360" src="http://www.youtube.com/embed/C2OQ51tu7W4?showinfo=0" frameborder="0" allowfullscreen></iframe> <iframe width="640" height="360" src="http://www.youtube.com/embed/C2OQ51tu7W4?showinfo=0" frameborder="0" allowfullscreen></iframe>
</div> </div>
</section> </section>
% if show_signup_immediately is not UNDEFINED:
<script type="text/javascript">
function dosignup(){
comp = document.getElementById('signup');
try { //in firefox
comp.click();
return;
} catch(ex) {}
try { // in old chrome
if(document.createEvent) {
var e = document.createEvent('MouseEvents');
e.initEvent( 'click', true, true );
comp.dispatchEvent(e);
return;
}
} catch(ex) {}
try { // in IE, safari
if(document.createEventObject) {
var evObj = document.createEventObject();
comp.fireEvent("onclick", evObj);
return;
}
} catch(ex) {}
}
$(window).load(dosignup);
</script>
% endif
...@@ -27,6 +27,9 @@ ...@@ -27,6 +27,9 @@
<span>Not enrolled? <a href="#signup-modal" class="close-login" rel="leanModal">Sign up.</a></span> <span>Not enrolled? <a href="#signup-modal" class="close-login" rel="leanModal">Sign up.</a></span>
<a href="#forgot-password-modal" rel="leanModal" class="pwd-reset">Forgot password?</a> <a href="#forgot-password-modal" rel="leanModal" class="pwd-reset">Forgot password?</a>
</p> </p>
<p>
<a href="${MITX_ROOT_URL}/openid/login/">login via openid</a>
</p>
</section> </section>
<div class="close-modal"> <div class="close-modal">
......
...@@ -72,7 +72,7 @@ $(function() { ...@@ -72,7 +72,7 @@ $(function() {
var new_email = $('#new_email_field').val(); var new_email = $('#new_email_field').val();
var new_password = $('#new_email_password').val(); var new_password = $('#new_email_password').val();
postJSON('/change_email',{"new_email":new_email, postJSON('/change_email',{"new_email":new_email,
"password":new_password}, "password":new_password},
function(data){ function(data){
if(data.success){ if(data.success){
...@@ -81,7 +81,7 @@ $(function() { ...@@ -81,7 +81,7 @@ $(function() {
$("#change_email_error").html(data.error); $("#change_email_error").html(data.error);
} }
}); });
log_event("profile", {"type":"email_change_request", log_event("profile", {"type":"email_change_request",
"old_email":"${email}", "old_email":"${email}",
"new_email":new_email}); "new_email":new_email});
return false; return false;
...@@ -91,7 +91,7 @@ $(function() { ...@@ -91,7 +91,7 @@ $(function() {
var new_name = $('#new_name_field').val(); var new_name = $('#new_name_field').val();
var rationale = $('#name_rationale_field').val(); var rationale = $('#name_rationale_field').val();
postJSON('/change_name',{"new_name":new_name, postJSON('/change_name',{"new_name":new_name,
"rationale":rationale}, "rationale":rationale},
function(data){ function(data){
if(data.success){ if(data.success){
...@@ -100,7 +100,7 @@ $(function() { ...@@ -100,7 +100,7 @@ $(function() {
$("#change_name_error").html(data.error); $("#change_name_error").html(data.error);
} }
}); });
log_event("profile", {"type":"name_change_request", log_event("profile", {"type":"name_change_request",
"new_name":new_name, "new_name":new_name,
"rationale":rationale}); "rationale":rationale});
return false; return false;
...@@ -125,9 +125,9 @@ $(function() { ...@@ -125,9 +125,9 @@ $(function() {
<ol class="chapters"> <ol class="chapters">
%for chapter in courseware_summary: %for chapter in courseware_summary:
%if not chapter['chapter'] == "hidden": %if not chapter['display_name'] == "hidden":
<li> <li>
<h2>${ chapter['chapter'] }</h2> <h2>${ chapter['display_name'] }</h2>
<ol class="sections"> <ol class="sections">
%for section in chapter['sections']: %for section in chapter['sections']:
...@@ -138,14 +138,13 @@ $(function() { ...@@ -138,14 +138,13 @@ $(function() {
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else "" percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
%> %>
<h3><a href="${reverse('courseware_section', kwargs={'course_id' : course.id, 'chapter' : chapter['chapter'], 'section' : section['section']})}"> <h3><a href="${reverse('courseware_section', kwargs={'course_id' : course.id, 'chapter' : chapter['url_name'], 'section' : section['url_name']})}">
${ section['display_name'] }</a> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</h3>
${ section['section'] }</a> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</h3>
${section['format']} ${section['format']}
%if 'due' in section and section['due']!="": %if 'due' in section and section['due']!="":
due ${section['due']} due ${section['due']}
%endif %endif
%if len(section['scores']) > 0: %if len(section['scores']) > 0:
<ol class="scores"> <ol class="scores">
${ "Problem Scores: " if section['graded'] else "Practice Scores: "} ${ "Problem Scores: " if section['graded'] else "Practice Scores: "}
...@@ -154,7 +153,7 @@ $(function() { ...@@ -154,7 +153,7 @@ $(function() {
%endfor %endfor
</ol> </ol>
%endif %endif
</li> <!--End section--> </li> <!--End section-->
%endfor %endfor
</ol> <!--End sections--> </ol> <!--End sections-->
...@@ -182,7 +181,7 @@ $(function() { ...@@ -182,7 +181,7 @@ $(function() {
</li> </li>
<li> <li>
Forum name: <strong>${username}</strong> Forum name: <strong>${username}</strong>
</li> </li>
<li> <li>
...@@ -216,7 +215,7 @@ $(function() { ...@@ -216,7 +215,7 @@ $(function() {
<form id="change_name_form"> <form id="change_name_form">
<div id="change_name_error"> </div> <div id="change_name_error"> </div>
<fieldset> <fieldset>
<p>To uphold the credibility of <span class="edx">edX</span> certificates, name changes must go through an approval process. A member of the course staff will review your request, and if approved, update your information. Please allow up to a week for your request to be processed. Thank you.</p> <p>To uphold the credibility of <span class="edx">edX</span> certificates, name changes must go through an approval process. A member of the course staff will review your request, and if approved, update your information. Please allow up to a week for your request to be processed. Thank you.</p>
<ul> <ul>
<li> <li>
<label>Enter your desired full name, as it will appear on the <span class="edx">edX</span> Certificate: </label> <label>Enter your desired full name, as it will appear on the <span class="edx">edX</span> Certificate: </label>
...@@ -235,7 +234,7 @@ $(function() { ...@@ -235,7 +234,7 @@ $(function() {
</div> </div>
<div id="change_email" class="leanModal_box"> <div id="change_email" class="leanModal_box">
<h1>Change e-mail</h1> <h1>Change e-mail</h1>
<div id="apply_name_change_error"></div> <div id="apply_name_change_error"></div>
<form id="change_email_form"> <form id="change_email_form">
<div id="change_email_error"> </div> <div id="change_email_error"> </div>
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
<div id="register_error" name="register_error"></div> <div id="register_error" name="register_error"></div>
<div class="input-group"> <div class="input-group">
% if has_extauth_info is UNDEFINED:
<label data-field="email">E-mail*</label> <label data-field="email">E-mail*</label>
<input name="email" type="email" placeholder="E-mail*"> <input name="email" type="email" placeholder="E-mail*">
<label data-field="password">Password*</label> <label data-field="password">Password*</label>
...@@ -27,6 +28,12 @@ ...@@ -27,6 +28,12 @@
<input name="username" type="text" placeholder="Public Username*"> <input name="username" type="text" placeholder="Public Username*">
<label data-field="name">Full Name</label> <label data-field="name">Full Name</label>
<input name="name" type="text" placeholder="Full Name*"> <input name="name" type="text" placeholder="Full Name*">
% else:
<p><i>Welcome</i> ${extauth_email}</p><br/>
<p><i>Enter a public username:</i></p>
<label data-field="username">Public Username*</label>
<input name="username" type="text" value="${extauth_username}" placeholder="Public Username*">
% endif
</div> </div>
<div class="input-group"> <div class="input-group">
...@@ -93,11 +100,13 @@ ...@@ -93,11 +100,13 @@
</div> </div>
</form> </form>
% if has_extauth_info is UNDEFINED:
<section class="login-extra"> <section class="login-extra">
<p> <p>
<span>Already have an account? <a href="#login-modal" class="close-signup" rel="leanModal">Login.</a></span> <span>Already have an account? <a href="#login-modal" class="close-signup" rel="leanModal">Login.</a></span>
</p> </p>
</section> </section>
% endif
</div> </div>
......
% if name is not UNDEFINED and name is not None: % if name is not UNDEFINED and name is not None:
<h1> ${name} </h1> <h1> ${display_name} </h1>
% endif % endif
<div id="video_${id}" class="video" data-streams="${streams}" data-caption-data-dir="${data_dir}"> <div id="video_${id}" class="video" data-streams="${streams}" data-caption-data-dir="${data_dir}">
......
...@@ -169,12 +169,18 @@ if settings.DEBUG: ...@@ -169,12 +169,18 @@ if settings.DEBUG:
## Jasmine ## Jasmine
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),) urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
urlpatterns += (
url(r'^openid/login/$', 'django_openid_auth.views.login_begin', name='openid-login'),
url(r'^openid/complete/$', 'external_auth.views.edXauth_openid_login_complete', name='openid-complete'),
url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'),
)
urlpatterns = patterns(*urlpatterns) urlpatterns = patterns(*urlpatterns)
if settings.DEBUG: if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
#Custom error pages #Custom error pages
handler404 = 'static_template_view.views.render_404' handler404 = 'static_template_view.views.render_404'
handler500 = 'static_template_view.views.render_500' handler500 = 'static_template_view.views.render_500'
......
...@@ -8,6 +8,7 @@ lxml ...@@ -8,6 +8,7 @@ lxml
boto boto
mako mako
python-memcached python-memcached
python-openid
path.py path.py
django_debug_toolbar django_debug_toolbar
-e git://github.com/MITx/django-pipeline.git#egg=django-pipeline -e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
...@@ -37,6 +38,7 @@ django-jasmine ...@@ -37,6 +38,7 @@ django-jasmine
django-keyedcache django-keyedcache
django-mako django-mako
django-masquerade django-masquerade
django-openid-auth
django-robots django-robots
django-ses django-ses
django-storages django-storages
......
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