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

Conflicts:
	lms/envs/dev.py
This commit is contained in:
Rocky Duan
2012-08-22 14:04:34 -07:00
70 changed files with 1086 additions and 523 deletions

View File

@@ -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))

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)}
#-----------------------------------------------------------------------------

View File

@@ -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):

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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