diff --git a/common/djangoapps/external_auth/views.py b/common/djangoapps/external_auth/views.py index 35e59db0ca..9e41d31c77 100644 --- a/common/djangoapps/external_auth/views.py +++ b/common/djangoapps/external_auth/views.py @@ -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)) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 0069935b0b..e7864337b3 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -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" />' % (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. diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py index 85b8ed3369..89b5dffd5e 100644 --- a/common/djangoapps/util/cache.py +++ b/common/djangoapps/util/cache.py @@ -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) diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py index 4b3050e227..86443520c2 100644 --- a/common/djangoapps/xmodule_modifiers.py +++ b/common/djangoapps/xmodule_modifiers.py @@ -112,11 +112,14 @@ def add_histogram(get_html, module, user): edit_link = "%s/%s/tree/master/%s" % (giturl,data_dir,filepath) else: edit_link = False + source_file = module.metadata.get('source_file','') # source used to generate the problem XML, eg latex or word staff_context = {'definition': module.definition.get('data'), 'metadata': json.dumps(module.metadata, indent=4), 'location': module.location, 'xqa_key': module.metadata.get('xqa_key',''), + 'source_file' : source_file, + 'source_url': '%s/%s/tree/master/%s' % (giturl,data_dir,source_file), 'category': str(module.__class__.__name__), 'element_id': module.location.html_id().replace('-','_'), 'edit_link': edit_link, diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index cc67389da9..b2d56b48ca 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -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)} #----------------------------------------------------------------------------- diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index e6da87b5c6..d2ed3912a4 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -390,9 +390,19 @@ class CapaModule(XModule): raise NotFoundError('Answer is not available') else: answers = self.lcp.get_question_answers() + # answers (eg ) 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): diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 5cc4a09165..3896affca1 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -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 diff --git a/common/lib/xmodule/xmodule/error_module.py b/common/lib/xmodule/xmodule/error_module.py index bdd7179a0a..f8e2467910 100644 --- a/common/lib/xmodule/xmodule/error_module.py +++ b/common/lib/xmodule/xmodule/error_module.py @@ -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 diff --git a/lms/djangoapps/branding/__init__.py b/lms/djangoapps/branding/__init__.py new file mode 100644 index 0000000000..3a4acd2964 --- /dev/null +++ b/lms/djangoapps/branding/__init__.py @@ -0,0 +1,52 @@ + +from xmodule.modulestore.django import modulestore +from xmodule.course_module import CourseDescriptor +from django.conf import settings + + +def get_subdomain(domain): + return domain.split(".")[0] + + +def get_visible_courses(domain=None): + """ + Return the set of CourseDescriptors that should be visible in this branded instance + """ + courses = [c for c in modulestore().get_courses() + if isinstance(c, CourseDescriptor)] + courses = sorted(courses, key=lambda course: course.number) + + if domain and settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): + subdomain = get_subdomain(domain) + if subdomain not in settings.COURSE_LISTINGS: + subdomain = 'default' + visible_ids = frozenset(settings.COURSE_LISTINGS[subdomain]) + return [course for course in courses if course.id in visible_ids] + else: + return courses + + +def get_university(domain=None): + """ + Return the university name specified for the domain, or None + if no university was specified + """ + if not settings.MITX_FEATURES['SUBDOMAIN_BRANDING'] or domain is None: + return None + + subdomain = get_subdomain(domain) + return settings.SUBDOMAIN_BRANDING.get(subdomain) + + +def get_logo_url(domain=None): + """ + Return the url for the branded logo image to be used + """ + university = get_university(domain) + + if university is None: + return '/static/images/header-logo.png' + + return '/static/images/{uni}-on-edx-logo.png'.format( + uni=university + ) diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py new file mode 100644 index 0000000000..e32eb92138 --- /dev/null +++ b/lms/djangoapps/branding/views.py @@ -0,0 +1,45 @@ +from django.conf import settings +from django.core.urlresolvers import reverse +from django.shortcuts import redirect +from django_future.csrf import ensure_csrf_cookie + +import student.views +import branding +import courseware.views +from util.cache import cache_if_anonymous + + +@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) + + university = branding.get_university(request.META.get('HTTP_HOST')) + if university is None: + return student.views.index(request, user=request.user) + + return courseware.views.university_profile(request, university) + + +@ensure_csrf_cookie +@cache_if_anonymous +def courses(request): + """ + Render the "find courses" page. If subdomain branding is on, this is the + university profile page, otherwise it's the edX courseware.views.courses page + """ + + university = branding.get_university(request.META.get('HTTP_HOST')) + if university is None: + return courseware.views.courses(request) + + return courseware.views.university_profile(request, university) diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py index 1d124972c7..122f9ebb54 100644 --- a/lms/djangoapps/course_wiki/course_nav.py +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -5,6 +5,7 @@ from django.http import Http404 from django.shortcuts import redirect from wiki.models import reverse as wiki_reverse +from courseware.access import has_access from courseware.courses import get_course_with_access @@ -135,7 +136,9 @@ def context_processor(request): try: course = get_course_with_access(request.user, course_id, 'load') - return {'course' : course} + staff_access = has_access(request.user, course, 'staff') + return {'course' : course, + 'staff_access': staff_access} except Http404: # We couldn't access the course for whatever reason. It is too late to change # the URL here, so we just leave the course context. The middleware shouldn't diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py index cfe802bbd7..ff6e08abf5 100644 --- a/lms/djangoapps/course_wiki/views.py +++ b/lms/djangoapps/course_wiki/views.py @@ -80,8 +80,8 @@ def course_wiki_redirect(request, course_id): urlpath = URLPath.create_article( root, course_slug, - title=course.number, - content="{0}\n===\nThis is the wiki for **{1}**'s _{2}_.".format(course.number, course.org, course.title), + title=course_slug, + content="This is the wiki for **{0}**'s _{1}_.".format(course.org, course.title), user_message="Course page automatically created.", user=None, ip_address=None, @@ -114,7 +114,7 @@ def get_or_create_root(): "===", "Visit a course wiki to add an article.")) - root = URLPath.create_root(title="edX Wiki", + root = URLPath.create_root(title="Wiki", content=starting_content) article = root.article article.group = None diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 281580cf33..dbe4ff376d 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -13,7 +13,6 @@ from xmodule.modulestore import Location from xmodule.timeparse import parse_time from xmodule.x_module import XModule, XModuleDescriptor - DEBUG_ACCESS = False log = logging.getLogger(__name__) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index c92cbb1425..e5ef915e25 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -13,6 +13,7 @@ from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError from static_replace import replace_urls, try_staticfiles_lookup from courseware.access import has_access +import branding log = logging.getLogger(__name__) @@ -141,9 +142,10 @@ def get_course_info_section(course, section_key): raise KeyError("Invalid about key " + str(section_key)) + # TODO: Fix this such that these are pulled in as extra course-specific tabs. # arjun will address this by the end of October if no one does so prior to -# then. +# then. def get_course_syllabus_section(course, section_key): """ This returns the snippet of html to be rendered on the syllabus page, @@ -178,24 +180,11 @@ def get_courses_by_university(user, domain=None): ''' # TODO: Clean up how 'error' is done. # filter out any courses that errored. - courses = [c for c in modulestore().get_courses() - if isinstance(c, CourseDescriptor)] - courses = sorted(courses, key=lambda course: course.number) - - if domain and settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): - subdomain = domain.split(".")[0] - if subdomain not in settings.COURSE_LISTINGS: - subdomain = 'default' - visible_courses = frozenset(settings.COURSE_LISTINGS[subdomain]) - else: - visible_courses = frozenset(c.id for c in courses) + visible_courses = branding.get_visible_courses(domain) universities = defaultdict(list) - for course in courses: + for course in visible_courses: if not has_access(user, course, 'see_exists'): continue - if course.id not in visible_courses: - continue universities[course.org].append(course) return universities - diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 65e30475f2..7967452647 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -1,5 +1,6 @@ import json import logging +import sys from django.conf import settings from django.contrib.auth.models import User @@ -15,10 +16,12 @@ from courseware.access import has_access from mitxmako.shortcuts import render_to_string from models import StudentModule, StudentModuleCache from static_replace import replace_urls +from xmodule.errortracker import exc_info_to_str from xmodule.exceptions import NotFoundError from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from xmodule.x_module import ModuleSystem +from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule log = logging.getLogger("mitx.courseware") @@ -73,6 +76,8 @@ def toc_for_course(user, request, course, active_chapter, active_section, course student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( course_id, user, course, depth=2) course = get_module(user, request, course.location, student_module_cache, course_id) + if course is None: + return None chapters = list() for chapter in course.get_display_items(): @@ -131,9 +136,9 @@ def get_section(course_module, chapter, section): return section_module - def get_module(user, request, location, student_module_cache, course_id, position=None): - ''' Get an instance of the xmodule class identified by location, + """ + Get an instance of the xmodule class identified by location, setting the state based on an existing StudentModule, or creating one if none exists. @@ -146,9 +151,22 @@ def get_module(user, request, location, student_module_cache, course_id, positio - position : extra information from URL for user-specified position within module - Returns: xmodule instance + Returns: xmodule instance, or None if the user does not have access to the + module. If there's an error, will try to return an instance of ErrorModule + if possible. If not possible, return None. + """ + try: + return _get_module(user, request, location, student_module_cache, course_id, position) + except: + # Something has gone terribly wrong, but still not letting it turn into a 500. + log.exception("Error in get_module") + return None - ''' +def _get_module(user, request, location, student_module_cache, course_id, position=None): + """ + Actually implement get_module. See docstring there for details. + """ + location = Location(location) descriptor = modulestore().get_instance(course_id, location) # Short circuit--if the user shouldn't have access, bail without doing any work @@ -198,7 +216,7 @@ def get_module(user, request, location, student_module_cache, course_id, positio 'callback_url': xqueue_callback_url, 'default_queuename': xqueue_default_queuename.replace(' ', '_')} - def _get_module(location): + def inner_get_module(location): """ Delegate to get_module. It does an access check, so may return None """ @@ -214,7 +232,7 @@ def get_module(user, request, location, student_module_cache, course_id, positio xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.system.resources_fs, - get_module=_get_module, + get_module=inner_get_module, user=user, # TODO (cpennington): This should be removed when all html from # a module is coming through get_html and is therefore covered @@ -226,7 +244,22 @@ def get_module(user, request, location, student_module_cache, course_id, positio system.set('position', position) system.set('DEBUG', settings.DEBUG) - module = descriptor.xmodule_constructor(system)(instance_state, shared_state) + try: + module = descriptor.xmodule_constructor(system)(instance_state, shared_state) + except: + log.exception("Error creating module from descriptor {0}".format(descriptor)) + + # make an ErrorDescriptor -- assuming that the descriptor's system is ok + import_system = descriptor.system + if has_access(user, location, 'staff'): + err_descriptor = ErrorDescriptor.from_xml(str(descriptor), import_system, + error_msg=exc_info_to_str(sys.exc_info())) + else: + err_descriptor = NonStaffErrorDescriptor.from_xml(str(descriptor), import_system, + error_msg=exc_info_to_str(sys.exc_info())) + + # Make an error module + return err_descriptor.xmodule_constructor(system)(None, None) module.get_html = replace_static_urls( wrap_xmodule(module.get_html, module, 'xmodule_display.html'), diff --git a/lms/envs/common.py b/lms/envs/common.py index a217f0e7b9..1cc6ae8d89 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -55,9 +55,14 @@ MITX_FEATURES = { # course_ids (see dev_int.py for an example) 'SUBDOMAIN_COURSE_LISTINGS' : False, + # When True, will override certain branding with university specific values + # Expects a SUBDOMAIN_BRANDING dictionary that maps the subdomain to the + # university to use for branding purposes + 'SUBDOMAIN_BRANDING': False, + # TODO: This will be removed once course-specific tabs are in place. see # courseware/courses.py - 'ENABLE_SYLLABUS' : True, + 'ENABLE_SYLLABUS' : True, 'ENABLE_TEXTBOOK' : True, 'ENABLE_DISCUSSION' : False, @@ -66,7 +71,7 @@ MITX_FEATURES = { 'ENABLE_SQL_TRACKING_LOGS': False, 'ENABLE_LMS_MIGRATION': False, - 'DISABLE_LOGIN_BUTTON': False, # used in systems where login is automatic, eg MIT SSL + 'DISABLE_LOGIN_BUTTON': False, # used in systems where login is automatic, eg MIT SSL # extrernal access methods 'ACCESS_REQUIRE_STAFF_FOR_COURSE': False, @@ -199,6 +204,11 @@ COURSE_SETTINGS = {'6.002x_Fall_2012': {'number' : '6.002x', # TODO (vshnayder): Will probably need to change as we get real access control in. LMS_MIGRATION_ALLOWED_IPS = [] +######################## subdomain specific settings ########################### +COURSE_LISTINGS = {} +SUBDOMAIN_BRANDING = {} + + ############################### XModule Store ################################## MODULESTORE = { 'default': { @@ -318,6 +328,7 @@ WIKI_ACCOUNT_HANDLING = False WIKI_EDITOR = 'course_wiki.editors.CodeMirror' WIKI_SHOW_MAX_CHILDREN = 0 # We don't use the little menu that shows children of an article in the breadcrumb WIKI_ANONYMOUS = False # Don't allow anonymous access until the styling is figured out +WIKI_CAN_CHANGE_PERMISSIONS = lambda article, user: user.has_perm('wiki.assign') ################################# Jasmine ################################### JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' diff --git a/lms/envs/dev.py b/lms/envs/dev.py index b269d293dd..d798815543 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -15,6 +15,8 @@ TEMPLATE_DEBUG = True MITX_FEATURES['DISABLE_START_DATES'] = True MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True +MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = True +MITX_FEATURES['SUBDOMAIN_BRANDING'] = True WIKI_ENABLED = True @@ -68,6 +70,28 @@ CACHE_TIMEOUT = 0 # Dummy secret key for dev SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' +COURSE_LISTINGS = { + 'default': ['BerkeleyX/CS169.1x/2012_Fall', + 'BerkeleyX/CS188.1x/2012_Fall', + 'HarvardX/CS50x/2012', + 'HarvardX/PH207x/2012_Fall', + 'MITx/3.091x/2012_Fall', + 'MITx/6.002x/2012_Fall', + 'MITx/6.00x/2012_Fall'], + 'berkeley': ['BerkeleyX/CS169.1x/Cal_2012_Fall', + 'BerkeleyX/CS188.1x/Cal_2012_Fall'], + 'harvard': ['HarvardX/CS50x/2012H'], + 'mit': [], + 'sjsu': ['MITx/6.002x-EE98/2012_Fall_SJSU'], +} + +SUBDOMAIN_BRANDING = { + 'sjsu': 'MITx', + 'mit': 'MITx', + 'berkeley': 'BerkeleyX', + 'harvard': 'HarvardX', +} + ################################ LMS Migration ################################# MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll diff --git a/lms/static/images/BerkeleyX-on-edx-logo.png b/lms/static/images/BerkeleyX-on-edx-logo.png new file mode 100644 index 0000000000..6c5a828503 Binary files /dev/null and b/lms/static/images/BerkeleyX-on-edx-logo.png differ diff --git a/lms/static/images/HarvardX-on-edx-logo.png b/lms/static/images/HarvardX-on-edx-logo.png new file mode 100644 index 0000000000..a4d40f52b6 Binary files /dev/null and b/lms/static/images/HarvardX-on-edx-logo.png differ diff --git a/lms/static/images/MITx-on-edx-logo.png b/lms/static/images/MITx-on-edx-logo.png new file mode 100644 index 0000000000..156a08bcbc Binary files /dev/null and b/lms/static/images/MITx-on-edx-logo.png differ diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index aa5d07fc44..95af67473a 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -100,13 +100,6 @@ header.global.slim { top: -12px; width: 1px; } - - a { - width: 48px; - height: 24px; - background: url(../images/small-header-logo.png) no-repeat !important; - } - } .find-courses-button { @@ -143,4 +136,4 @@ header.global.slim { font-weight: bold; letter-spacing: 0; } -} \ No newline at end of file +} diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index e88d5e79af..7fa6df1281 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -203,6 +203,17 @@ section.wiki { font-size: 0.9em; font-family: Monaco, monospace; } + + .toc { + background-color: $sidebar-color; + padding: 9px; + margin: 10px 0; + @include border-radius(5px); + + ul { + margin: 0; + } + } } @@ -220,7 +231,7 @@ section.wiki { padding: 40px 40px; @include box-sizing(border-box); - .timestamp { + .timestamp{ margin-top: 15px; padding: 15px 0 0 10px; border-top: 1px solid $light-gray; @@ -234,6 +245,26 @@ section.wiki { .date { font-size: 0.9em; } + + } + + .see-children { + padding: 15px 0 0; + border-top: 1px solid $light-gray; + margin-top: 15px; + + a { + display: block; + padding: 2px 4px 2px 10px; + border-radius: 3px; + font-size: 0.9em; + line-height: 25px; + + &:hover { + background-color: #f6f6f6; + text-decoration: none; + } + } } } @@ -663,6 +694,59 @@ section.wiki { margin-top: 9px; } + /*----------------- + + Directory + + -----------------*/ + .directory-toolbar { + background-color: $sidebar-color; + padding: 9px; + margin: 0 -9px 20px; + @include border-radius(5px); + + .well-small { + @include clearfix; + + a { + @include inline-block; + } + } + + + p { + font-size: 0.9em; + color: #aaa; + } + } + + .filter-clear { + margin-right: 10px; + margin-top: 10px; + font-size: .9em; + + a { + color: #aaa; + + &:hover { + color: #777; + } + } + } + + .table.table-striped { + width: 100%; + margin-top: 20px; + + th, td { + border-bottom: 1px solid $light-gray; + padding: 8px; + } + + tr:nth-child(even) { + background: #F6F6F6; + } + } + @@ -788,6 +872,39 @@ section.wiki { text-decoration: none; } } + + .missing { + max-width: 400px; + margin: lh(2) auto; + display: block; + overflow: hidden; + background: $pink; + padding: lh(); + @include box-shadow(inset 0 0 0 1px lighten($pink, 10%)); + border: 1px solid darken($pink, 15%); + + p { + color: #fff; + + a { + display: block; + background: darken($pink, 8%); + margin: lh() (-(lh())) (-(lh())); + padding: lh(); + border-top: 1px solid darken($pink, 15%); + color: #fff; + font-weight: bold; + font-size: em(18); + @include transition; + text-align: center; + -webkit-font-smoothing: antialiased; + + &:hover { + background: darken($pink, 12%); + } + } + } + } } .modal-backdrop { diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index 116761ddc8..49c9ac250b 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -19,7 +19,7 @@ header.global { h1.logo { float: left; - margin: 6px 15px 0px 0px; + margin: 0px 15px 0px 0px; padding-right: 20px; position: relative; @@ -46,12 +46,7 @@ header.global { } a { - @include background-image(url('/static/images/header-logo.png')); - background-position: 0 0; - background-repeat: no-repeat; display: block; - height: 31px; - width: 64px; } } diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index d5bdb9b302..58998d7c1d 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -1,6 +1,6 @@ <%inherit file="/main.html" /> <%namespace name='static' file='/static_content.html'/> -<%block name="bodyclass">courseware +<%block name="bodyclass">courseware ${course.css_class} <%block name="title">${course.number} Courseware <%block name="headextra"> diff --git a/lms/templates/footer.html b/lms/templates/footer.html index 85ed6e1769..52c2b45526 100644 --- a/lms/templates/footer.html +++ b/lms/templates/footer.html @@ -6,7 +6,7 @@