Merge branch 'master' of github.com:MITx/mitx into merge
Conflicts: lms/envs/dev.py
This commit is contained in:
@@ -157,7 +157,7 @@ def edXauth_signup(request, eamap=None):
|
||||
|
||||
log.debug('ExtAuth: doing signup for %s' % eamap.external_email)
|
||||
|
||||
return student_views.main_index(request, extra_context=context)
|
||||
return student_views.index(request, extra_context=context)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# MIT SSL
|
||||
@@ -193,7 +193,7 @@ def edXauth_ssl_login(request):
|
||||
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.
|
||||
Else continues on with student.views.index, and no authentication.
|
||||
"""
|
||||
certkey = "SSL_CLIENT_S_DN" # specify the request.META field to use
|
||||
|
||||
@@ -207,7 +207,7 @@ def edXauth_ssl_login(request):
|
||||
pass
|
||||
if not cert:
|
||||
# no certificate information - go onward to main index
|
||||
return student_views.main_index(request)
|
||||
return student_views.index(request)
|
||||
|
||||
(user, email, fullname) = ssl_dn_extract_info(cert)
|
||||
|
||||
@@ -217,4 +217,4 @@ def edXauth_ssl_login(request):
|
||||
credentials=cert,
|
||||
email=email,
|
||||
fullname=fullname,
|
||||
retfun = functools.partial(student_views.main_index, request))
|
||||
retfun = functools.partial(student_views.index, request))
|
||||
|
||||
@@ -22,7 +22,6 @@ from django.db import IntegrityError
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import redirect
|
||||
from mitxmako.shortcuts import render_to_response, render_to_string
|
||||
from django.core.urlresolvers import reverse
|
||||
from bs4 import BeautifulSoup
|
||||
from django.core.cache import cache
|
||||
|
||||
@@ -30,7 +29,6 @@ from django_future.csrf import ensure_csrf_cookie
|
||||
from student.models import (Registration, UserProfile,
|
||||
PendingNameChange, PendingEmailChange,
|
||||
CourseEnrollment)
|
||||
from util.cache import cache_if_anonymous
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -54,23 +52,7 @@ def csrf_token(context):
|
||||
' name="csrfmiddlewaretoken" value="%s" /></div>' % (csrf_token))
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def index(request):
|
||||
|
||||
''' Redirects to main page -- info page if user authenticated, or marketing if not
|
||||
'''
|
||||
|
||||
if settings.COURSEWARE_ENABLED and request.user.is_authenticated():
|
||||
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(request, user=request.user)
|
||||
|
||||
def main_index(request, extra_context={}, user=None):
|
||||
def index(request, extra_context={}, user=None):
|
||||
'''
|
||||
Render the edX main page.
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from functools import wraps
|
||||
|
||||
from django.core import cache
|
||||
|
||||
|
||||
# If we can't find a 'general' CACHE defined in settings.py, we simply fall back
|
||||
# to returning the default cache. This will happen with dev machines.
|
||||
try:
|
||||
@@ -41,7 +42,10 @@ def cache_if_anonymous(view_func):
|
||||
def _decorated(request, *args, **kwargs):
|
||||
if not request.user.is_authenticated():
|
||||
#Use the cache
|
||||
cache_key = "cache_if_anonymous." + request.path
|
||||
# same view accessed through different domain names may
|
||||
# return different things, so include the domain name in the key.
|
||||
domain = str(request.META.get('HTTP_HOST')) + '.'
|
||||
cache_key = domain + "cache_if_anonymous." + request.path
|
||||
response = cache.get(cache_key)
|
||||
if not response:
|
||||
response = view_func(request, *args, **kwargs)
|
||||
|
||||
@@ -557,7 +557,7 @@ class ChoiceResponse(LoncapaResponse):
|
||||
return CorrectMap(self.answer_id, 'incorrect')
|
||||
|
||||
def get_answers(self):
|
||||
return {self.answer_id: self.correct_choices}
|
||||
return {self.answer_id: list(self.correct_choices)}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -390,9 +390,19 @@ class CapaModule(XModule):
|
||||
raise NotFoundError('Answer is not available')
|
||||
else:
|
||||
answers = self.lcp.get_question_answers()
|
||||
|
||||
# answers (eg <solution>) may have embedded images
|
||||
answers = dict( (k,self.system.replace_urls(answers[k], self.metadata['data_dir'])) for k in answers )
|
||||
return {'answers': answers}
|
||||
# but be careful, some problems are using non-string answer dicts
|
||||
new_answers = dict()
|
||||
for answer_id in answers:
|
||||
try:
|
||||
new_answer = {answer_id: self.system.replace_urls(answers[answer_id], self.metadata['data_dir'])}
|
||||
except TypeError:
|
||||
log.debug('Unable to perform URL substitution on answers[%s]: %s' % (answer_id, answers[answer_id]))
|
||||
new_answer = {answer_id: answers[answer_id]}
|
||||
new_answers.update(new_answer)
|
||||
|
||||
return {'answers': new_answers}
|
||||
|
||||
# Figure out if we should move these to capa_problem?
|
||||
def get_problem(self, get):
|
||||
|
||||
@@ -60,6 +60,8 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
def __init__(self, system, definition=None, **kwargs):
|
||||
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
|
||||
self.textbooks = self.definition['data']['textbooks']
|
||||
|
||||
self.wiki_slug = self.definition['data']['wiki_slug'] or self.location.course
|
||||
|
||||
msg = None
|
||||
if self.start is None:
|
||||
@@ -94,8 +96,19 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
for textbook in xml_object.findall("textbook"):
|
||||
textbooks.append(cls.Textbook.from_xml_object(textbook))
|
||||
xml_object.remove(textbook)
|
||||
|
||||
#Load the wiki tag if it exists
|
||||
wiki_slug = None
|
||||
wiki_tag = xml_object.find("wiki")
|
||||
if wiki_tag is not None:
|
||||
wiki_slug = wiki_tag.attrib.get("slug", default=None)
|
||||
xml_object.remove(wiki_tag)
|
||||
|
||||
definition = super(CourseDescriptor, cls).definition_from_xml(xml_object, system)
|
||||
|
||||
definition.setdefault('data', {})['textbooks'] = textbooks
|
||||
definition['data']['wiki_slug'] = wiki_slug
|
||||
|
||||
return definition
|
||||
|
||||
def has_started(self):
|
||||
@@ -197,6 +210,19 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
def start_date_text(self):
|
||||
return time.strftime("%b %d, %Y", self.start)
|
||||
|
||||
# An extra property is used rather than the wiki_slug/number because
|
||||
# there are courses that change the number for different runs. This allows
|
||||
# courses to share the same css_class across runs even if they have
|
||||
# different numbers.
|
||||
#
|
||||
# TODO get rid of this as soon as possible or potentially build in a robust
|
||||
# way to add in course-specific styling. There needs to be a discussion
|
||||
# about the right way to do this, but arjun will address this ASAP. Also
|
||||
# note that the courseware template needs to change when this is removed.
|
||||
@property
|
||||
def css_class(self):
|
||||
return self.metadata.get('css_class', '')
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self.display_name
|
||||
@@ -205,10 +231,6 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
def number(self):
|
||||
return self.location.course
|
||||
|
||||
@property
|
||||
def wiki_slug(self):
|
||||
return self.location.course
|
||||
|
||||
@property
|
||||
def org(self):
|
||||
return self.location.org
|
||||
|
||||
@@ -2,18 +2,54 @@ nav.sequence-nav {
|
||||
// TODO (cpennington): This doesn't work anymore. XModules aren't able to
|
||||
// import from external sources.
|
||||
@extend .topbar;
|
||||
border-bottom: 1px solid $border-color;
|
||||
@include border-top-right-radius(4px);
|
||||
margin: 0 0 lh() (-(lh()));
|
||||
margin: -4px 0 30px;
|
||||
position: relative;
|
||||
border-bottom: none;
|
||||
|
||||
.left-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
width: 20px;
|
||||
height: 46px;
|
||||
@include linear-gradient(left, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0));
|
||||
background-color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.right-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
width: 20px;
|
||||
height: 46px;
|
||||
@include linear-gradient(right, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0));
|
||||
background-color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sequence-list-wrapper {
|
||||
position: relative;
|
||||
z-index: 9999;
|
||||
border: 1px solid #ccc;
|
||||
height: 44px;
|
||||
margin: 0 30px;
|
||||
@include linear-gradient(top, #ddd, #eee);
|
||||
overflow: hidden;
|
||||
@include box-shadow(0 1px 3px rgba(0, 0, 0, .1) inset);
|
||||
}
|
||||
|
||||
ol {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@include box-sizing(border-box);
|
||||
display: table;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding-left: 3px;
|
||||
padding-right: flex-grid(1, 9);
|
||||
padding: 0 10px;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
@@ -25,42 +61,44 @@ nav.sequence-nav {
|
||||
min-width: 20px;
|
||||
|
||||
a {
|
||||
background-position: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin: 4px auto;
|
||||
background-position: center 10px;
|
||||
background-repeat: no-repeat;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
@include border-radius(35px);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 10px;
|
||||
padding: 15px 0 14px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
@include transition();
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: #F3F3F3;
|
||||
}
|
||||
|
||||
&.visited {
|
||||
background-color: #F3F3F3;
|
||||
|
||||
&:hover {
|
||||
background-position: center center;
|
||||
}
|
||||
background-position: center 10px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: $border-color;
|
||||
@include box-shadow(0 2px 0 #fff);
|
||||
background-color: #fff;
|
||||
z-index: 9;
|
||||
|
||||
// &:after {
|
||||
// content: '▲';
|
||||
// position: absolute;
|
||||
// top: 28px;
|
||||
// left: 50%;
|
||||
// z-index: 9999;
|
||||
// margin-left: -5px;
|
||||
// font-size: 12px;
|
||||
// color: #aaa;
|
||||
// }
|
||||
|
||||
&:hover {
|
||||
background-position: center;
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,20 +209,24 @@ nav.sequence-nav {
|
||||
}
|
||||
|
||||
ul {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
list-style: none;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: flex-grid(1, 9);
|
||||
border: 1px solid $border-color;
|
||||
border-bottom: 0;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: none;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
position: absolute;
|
||||
margin-bottom: 0;
|
||||
width: 50%;
|
||||
height: 44px;
|
||||
width: 70px;
|
||||
border: 1px solid #ccc;
|
||||
@include linear-gradient(top, #eee, #ddd);
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .7) inset);
|
||||
|
||||
&.prev, &.next {
|
||||
|
||||
@@ -192,14 +234,13 @@ nav.sequence-nav {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
height: 10px;
|
||||
padding: 15px 0 14px;
|
||||
height: 34px;
|
||||
width: 40px;
|
||||
text-indent: -9999px;
|
||||
@include transition(all, .2s, $ease-in-out-quad);
|
||||
|
||||
&:hover {
|
||||
opacity: .5;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@@ -210,15 +251,23 @@ nav.sequence-nav {
|
||||
}
|
||||
|
||||
&.prev {
|
||||
left: -10px;
|
||||
border-radius: 35px 0 0 35px;
|
||||
|
||||
a {
|
||||
background-image: url('../images/sequence-nav/previous-icon.png');
|
||||
background-position: center 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&.next {
|
||||
right: -10px;
|
||||
border-radius: 0 35px 35px 0;
|
||||
|
||||
a {
|
||||
border-left: 1px solid lighten($border-color, 10%);
|
||||
margin-left: 30px;
|
||||
background-image: url('../images/sequence-nav/next-icon.png');
|
||||
background-position: center 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
div.video {
|
||||
@include clearfix();
|
||||
background: #f3f3f3;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
display: block;
|
||||
margin: 0 0 0 (-(lh()));
|
||||
padding: 6px lh();
|
||||
margin: 0 -12px;
|
||||
padding: 12px;
|
||||
border-radius: 5px;
|
||||
|
||||
article.video-wrapper {
|
||||
float: left;
|
||||
@@ -401,6 +400,7 @@ div.video {
|
||||
overflow: auto;
|
||||
width: flex-grid(3, 9);
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
|
||||
li {
|
||||
border: 0;
|
||||
|
||||
@@ -15,18 +15,39 @@ from xmodule.errortracker import exc_info_to_str
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# NOTE: This is not the most beautiful design in the world, but there's no good
|
||||
# way to tell if the module is being used in a staff context or not. Errors that get discovered
|
||||
# at course load time are turned into ErrorDescriptor objects, and automatically hidden from students.
|
||||
# Unfortunately, we can also have errors when loading modules mid-request, and then we need to decide
|
||||
# what to show, and the logic for that belongs in the LMS (e.g. in get_module), so the error handler
|
||||
# decides whether to create a staff or not-staff module.
|
||||
|
||||
class ErrorModule(XModule):
|
||||
def get_html(self):
|
||||
'''Show an error.
|
||||
'''Show an error to staff.
|
||||
TODO (vshnayder): proper style, divs, etc.
|
||||
'''
|
||||
# staff get to see all the details
|
||||
return self.system.render_template('module-error.html', {
|
||||
'staff_access' : True,
|
||||
'data' : self.definition['data']['contents'],
|
||||
'error' : self.definition['data']['error_msg'],
|
||||
})
|
||||
|
||||
|
||||
class NonStaffErrorModule(XModule):
|
||||
def get_html(self):
|
||||
'''Show an error to a student.
|
||||
TODO (vshnayder): proper style, divs, etc.
|
||||
'''
|
||||
# staff get to see all the details
|
||||
return self.system.render_template('module-error.html', {
|
||||
'staff_access' : False,
|
||||
'data' : "",
|
||||
'error' : "",
|
||||
})
|
||||
|
||||
|
||||
class ErrorDescriptor(EditingDescriptor):
|
||||
"""
|
||||
Module that provides a raw editing view of broken xml.
|
||||
@@ -99,3 +120,9 @@ class ErrorDescriptor(EditingDescriptor):
|
||||
err_node = etree.SubElement(root, 'error_msg')
|
||||
err_node.text = self.definition['data']['error_msg']
|
||||
return etree.tostring(root)
|
||||
|
||||
class NonStaffErrorDescriptor(ErrorDescriptor):
|
||||
"""
|
||||
Module that provides non-staff error messages.
|
||||
"""
|
||||
module_class = NonStaffErrorModule
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 243 B After Width: | Height: | Size: 1.0 KiB |
Reference in New Issue
Block a user