There has been an error on the MITx servers
+We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible. Please email us at technical@mitx.mit.edu to report any problems or downtime.
+diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..9730219ef2 --- /dev/null +++ b/Gemfile @@ -0,0 +1,10 @@ +source :rubygems + +gem 'guard', '~> 1.0.3' +gem 'guard-process', '~> 1.0.3' +gem 'guard-coffeescript', '~> 0.6.0' +gem 'sass', '3.1.15' +gem 'guard-sass', :github => 'sikachu/guard-sass' +gem 'bourbon', '~> 1.3.6' +gem 'libnotify', '~> 0.7.2' +gem 'ruby_gntp', '~> 0.3.4' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..e63f6edcbd --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,49 @@ +GIT + remote: git://github.com/sikachu/guard-sass.git + revision: 2a646996d7fdaa2fabf5f65ba700bd8b02f14c1b + specs: + guard-sass (0.6.0) + guard (>= 0.4.0) + sass (>= 3.1) + +GEM + remote: http://rubygems.org/ + specs: + bourbon (1.3.6) + sass (>= 3.1) + coffee-script (2.2.0) + coffee-script-source + execjs + coffee-script-source (1.3.3) + execjs (1.3.2) + multi_json (~> 1.0) + ffi (1.0.11) + guard (1.0.3) + ffi (>= 0.5.0) + thor (>= 0.14.6) + guard-coffeescript (0.6.0) + coffee-script (>= 2.2.0) + guard (>= 0.8.3) + guard-process (1.0.3) + ffi (~> 1.0.9) + guard (>= 0.4.2) + spoon (~> 0.0.1) + libnotify (0.7.2) + multi_json (1.3.5) + ruby_gntp (0.3.4) + sass (3.1.15) + spoon (0.0.1) + thor (0.15.2) + +PLATFORMS + ruby + +DEPENDENCIES + bourbon (~> 1.3.6) + guard (~> 1.0.3) + guard-coffeescript (~> 0.6.0) + guard-process (~> 1.0.3) + guard-sass! + libnotify (~> 0.7.2) + ruby_gntp (~> 0.3.4) + sass (= 3.1.15) diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000000..46dbf0d367 --- /dev/null +++ b/Guardfile @@ -0,0 +1,19 @@ +require 'bourbon' + +# Helper method +def production? + @@options[:group].include? 'production' +end + +guard :coffeescript, :name => :jasmine, :input => 'templates/coffee/spec', :all_on_start => production? + +guard :coffeescript, :input => 'templates/coffee/src', :noop => true +guard :process, :name => :coffeescript, :command => "coffee -j static/js/application.js -c templates/coffee/src" do + watch(%r{^templates/coffee/src/(.+)\.coffee$}) +end + +if production? + guard :sass, :input => 'templates/sass', :output => 'static/css', :style => :compressed, :all_on_start => true +else + guard :sass, :input => 'templates/sass', :output => 'static/css', :style => :nested, :line_numbers => true +end diff --git a/djangoapps/courseware/views.py b/djangoapps/courseware/views.py index 5f67e599c0..56409b65a5 100644 --- a/djangoapps/courseware/views.py +++ b/djangoapps/courseware/views.py @@ -1,5 +1,6 @@ import logging import urllib +import json from django.conf import settings from django.core.context_processors import csrf @@ -16,6 +17,8 @@ from lxml import etree from module_render import render_module, make_track_function, I4xSystem from models import StudentModule from student.models import UserProfile +from util.errors import record_exception +from util.views import accepts from multicourse import multicourse_settings import courseware.content_parser as content_parser @@ -110,12 +113,16 @@ def render_section(request, section): if 'coursename' in request.session: coursename = request.session['coursename'] else: coursename = None -# try: - dom = content_parser.section_file(user, section, coursename) - #except: - # raise Http404 + try: + dom = content_parser.section_file(user, section, coursename) + except: + record_exception(log, "Unable to parse courseware xml") + return render_to_response('courseware-error.html', {}) - accordion=render_accordion(request, '', '', '') + context = { + 'csrf': csrf(request)['csrf_token'], + 'accordion': render_accordion(request, '', '', '') + } module_ids = dom.xpath("//@id") @@ -125,15 +132,20 @@ def render_section(request, section): else: module_object_preload = [] - module=render_module(user, request, dom, module_object_preload) + try: + module = render_module(user, request, dom, module_object_preload) + except: + record_exception(log, "Unable to load module") + context.update({ + 'init': '', + 'content': render_to_string("module-error.html", {}), + }) + return render_to_response('courseware.html', context) - if 'init_js' not in module: - module['init_js']='' - - context={'init':module['init_js'], - 'accordion':accordion, - 'content':module['content'], - 'csrf':csrf(request)['csrf_token']} + context.update({ + 'init':module.get('init_js', ''), + 'content':module['content'], + }) result = render_to_response('courseware.html', context) return result @@ -167,22 +179,22 @@ def index(request, course=None, chapter="Using the System", section="Hints"): if not multicourse_settings.is_valid_course(course): return redirect('/') - #import logging - #log = logging.getLogger("mitx") - #log.info( "DEBUG: "+str(user) ) - request.session['coursename'] = course # keep track of current course being viewed in django's request.session - dom = content_parser.course_file(user,course) # also pass course to it, for course-specific XML path + try: + dom = content_parser.course_file(user,course) # also pass course to it, for course-specific XML path + except: + record_exception(log, "Unable to parse courseware xml") + return render_to_response('courseware-error.html', {}) + dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]/*[1]", course=course, chapter=chapter, section=section) + if len(dom_module) == 0: module = None else: module = dom_module[0] - accordion=render_accordion(request, course, chapter, section) - module_ids = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]//@id", course=course, chapter=chapter, section=section) @@ -191,18 +203,27 @@ def index(request, course=None, chapter="Using the System", section="Hints"): module_id__in=module_ids)) else: module_object_preload = [] - - module=render_module(user, request, module, module_object_preload) + context = { + 'csrf': csrf(request)['csrf_token'], + 'accordion': render_accordion(request, course, chapter, section), + 'COURSE_TITLE':multicourse_settings.get_course_title(course), + } - if 'init_js' not in module: - module['init_js']='' + try: + module = render_module(user, request, module, module_object_preload) + except: + record_exception(log, "Unable to load module") + context.update({ + 'init': '', + 'content': render_to_string("module-error.html", {}), + }) + return render_to_response('courseware.html', context) - context={'init':module['init_js'], - 'accordion':accordion, - 'content':module['content'], - 'COURSE_TITLE':multicourse_settings.get_course_title(course), - 'csrf':csrf(request)['csrf_token']} + context.update({ + 'init': module.get('init_js', ''), + 'content': module['content'], + }) result = render_to_response('courseware.html', context) return result @@ -234,7 +255,15 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): else: coursename = None # Grab the XML corresponding to the request from course.xml - xml = content_parser.module_xml(request.user, module, 'id', id, coursename) + try: + xml = content_parser.module_xml(request.user, module, 'id', id, coursename) + except: + record_exception(log, "Unable to load module during ajax call") + if accepts(request, 'text/html'): + return render_to_response("module-error.html", {}) + else: + response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) + return response # Create the module system = I4xSystem(track_function = make_track_function(request), @@ -242,10 +271,20 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): ajax_url = ajax_url, filestore = None ) - instance=courseware.modules.get_module_class(module)(system, - xml, - id, - state=oldstate) + + try: + instance=courseware.modules.get_module_class(module)(system, + xml, + id, + state=oldstate) + except: + record_exception(log, "Unable to load module instance during ajax call") + if accepts(request, 'text/html'): + return render_to_response("module-error.html", {}) + else: + response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) + return response + # Let the module handle the AJAX ajax_return=instance.handle_ajax(dispatch, request.POST) # Save the state back to the database diff --git a/envs/common.py b/envs/common.py index 6c596dc7fa..66767e9055 100644 --- a/envs/common.py +++ b/envs/common.py @@ -79,6 +79,7 @@ TEMPLATE_DIRS = ( TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', + 'django.core.context_processors.static', 'askbot.context.application_settings', 'django.contrib.messages.context_processors.messages', #'django.core.context_processors.i18n', @@ -105,6 +106,20 @@ DEV_CONTENT = True TRACK_MAX_EVENT = 10000 DEBUG_TRACK_LOG = False +MITX_ROOT_URL = '' + +COURSE_NAME = "6.002_Spring_2012" +COURSE_NUMBER = "6.002x" +COURSE_TITLE = "Circuits and Electronics" + +ROOT_URLCONF = 'urls' + +### Dark code. Should be enabled in local settings for devel. + +ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome) + +### + ############################### DJANGO BUILT-INS ############################### # Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here DEBUG = False @@ -162,14 +177,22 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years ################################### ASKBOT ##################################### +LIVESETTINGS_OPTIONS['MITX_ROOT_URL'] = MITX_ROOT_URL +skin_settings = LIVESETTINGS_OPTIONS[1]['SETTINGS']['GENERAL_SKIN_SETTINGS'] +skin_settings['SITE_FAVICON'] = unicode(MITX_ROOT_URL) + skin_settings['SITE_FAVICON'] +skin_settings['SITE_LOGO_URL'] = unicode(MITX_ROOT_URL) + skin_settings['SITE_LOGO_URL'] +skin_settings['LOCAL_LOGIN_ICON'] = unicode(MITX_ROOT_URL) + skin_settings['LOCAL_LOGIN_ICON'] +LIVESETTINGS_OPTIONS[1]['SETTINGS']['LOGIN_PROVIDERS']['WORDPRESS_SITE_ICON'] = unicode(MITX_ROOT_URL) + LIVESETTINGS_OPTIONS[1]['SETTINGS']['LOGIN_PROVIDERS']['WORDPRESS_SITE_ICON'] +LIVESETTINGS_OPTIONS[1]['SETTINGS']['LICENSE_SETTINGS']['LICENSE_LOGO_URL'] = unicode(MITX_ROOT_URL) + LIVESETTINGS_OPTIONS[1]['SETTINGS']['LICENSE_SETTINGS']['LICENSE_LOGO_URL'] + ASKBOT_EXTRA_SKINS_DIR = ASKBOT_ROOT / "askbot" / "skins" ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff') ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 # result in bytes CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True ASKBOT_URL = 'discussion/' -LOGIN_REDIRECT_URL = '/' -LOGIN_URL = '/' +LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/' +LOGIN_URL = MITX_ROOT_URL + '/' ALLOW_UNICODE_SLUGS = False ASKBOT_USE_STACKEXCHANGE_URLS = False # mimic url scheme of stackexchange @@ -184,6 +207,9 @@ djcelery.setup_loader() SIMPLE_WIKI_REQUIRE_LOGIN_EDIT = True SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False +################################# Jasmine ################################### +JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/templates/coffee' + ################################# Middleware ################################### # List of finder classes that know how to find static files in # various locations. @@ -202,6 +228,7 @@ TEMPLATE_LOADERS = ( MIDDLEWARE_CLASSES = ( 'util.middleware.ExceptionLoggingMiddleware', + 'util.middleware.AcceptMiddleware', 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -289,6 +316,9 @@ INSTALLED_APPS = ( 'track', 'util', + # For testing + 'django_jasmine', + # For Askbot 'django.contrib.sitemaps', 'django.contrib.admin', diff --git a/lib/util/errors.py b/lib/util/errors.py new file mode 100644 index 0000000000..c662003a11 --- /dev/null +++ b/lib/util/errors.py @@ -0,0 +1,7 @@ +import newrelic.agent +import sys + +def record_exception(logger, msg, params={}, ignore_errors=[]): + logger.exception(msg) + newrelic.agent.record_exception(*sys.exc_info()) + diff --git a/lib/util/middleware.py b/lib/util/middleware.py index 342fff1790..eeffa2668c 100644 --- a/lib/util/middleware.py +++ b/lib/util/middleware.py @@ -13,3 +13,4 @@ class ExceptionLoggingMiddleware(object): def process_exception(self, request, exception): log.exception(exception) return HttpResponseServerError("Server Error - Please try again later.") + diff --git a/lib/util/views.py b/lib/util/views.py index d95f1e9a22..a071208e92 100644 --- a/lib/util/views.py +++ b/lib/util/views.py @@ -66,3 +66,29 @@ def mitxhome(request): if settings.ENABLE_MULTICOURSE: return render_to_response("mitxhome.html", {}) return info(request) + +# From http://djangosnippets.org/snippets/1042/ +def parse_accept_header(accept): + """Parse the Accept header *accept*, returning a list with pairs of + (media_type, q_value), ordered by q values. + """ + result = [] + for media_range in accept.split(","): + parts = media_range.split(";") + media_type = parts.pop(0) + media_params = [] + q = 1.0 + for part in parts: + (key, value) = part.lstrip().split("=", 1) + if key == "q": + q = float(value) + else: + media_params.append((key, value)) + result.append((media_type, tuple(media_params), q)) + result.sort(lambda x, y: -cmp(x[2], y[2])) + return result + +def accepts(request, media_type): + """Return whether this request has an Accept header that matches type""" + accept = parse_accept_header(request.META.get("HTTP_ACCEPT", "")) + return media_type in [t for (t, p, q) in accept] diff --git a/requirements.txt b/requirements.txt index 10564b09db..4135b6aab4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,4 @@ fs django-jasmine beautifulsoup requests +newrelic diff --git a/sass/README b/sass/README deleted file mode 100644 index f1a4618963..0000000000 --- a/sass/README +++ /dev/null @@ -1,17 +0,0 @@ -This project is using Sass to generate it's CSS. Sass is a CSS preprocessor that allows for faster development of CSS. For more information about sass: http://sass-lang.com - -To use sass all you need to do is enter: -$ gem install sass - -We are also using Bourbon with sass. They are a generic set of mixins, and functions that allow for more rapid development of CSS3. Find out more about bourbon here: https://github.com/thoughtbot/bourbon - -To use bourbon you need to install it with: -$ gem install bourbon - -Then to generate Sass files cd to templates directory and watch the sass files for development: -$ sass --watch sass:../static/css/ -r ./sass/bourbon/lib/bourbon.rb - -To generate a compressed css file for production: -$ sass --watch sass:../static/css/ -r ./sass/bourbon/lib/bourbon.rb --style :compressed - -These will automatically generate the CSS files on save. diff --git a/sass/base/_extends.scss b/sass/base/_extends.scss index 11409be60c..1b46231e35 100644 --- a/sass/base/_extends.scss +++ b/sass/base/_extends.scss @@ -124,10 +124,8 @@ h1.top-header { } h3 { - @extend .bottom-border; background: none; border: none; - border-bottom: 1px solid #d3d3d3; color: #000; font-weight: normal; margin: 0; @@ -140,11 +138,6 @@ h1.top-header { padding: 7px 7px 7px 30px; text-decoration: none; @include transition(); - - &:hover { - background: #efefef; - @include box-shadow(0 1px 0 #fff); - } } span.ui-icon { @@ -153,9 +146,9 @@ h1.top-header { &.active { background: none; - border: 0; - border-bottom: 1px solid #bbb; - @include box-shadow(none); + @include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(225,225,225))); + border-bottom: 1px solid #d3d3d3; + @include box-shadow(inset 0 1px 0 0 #eee); color: #000; font-weight: bold; diff --git a/sass/courseware/_sidebar.scss b/sass/courseware/_sidebar.scss index 8327da1548..845821c5f4 100644 --- a/sass/courseware/_sidebar.scss +++ b/sass/courseware/_sidebar.scss @@ -5,11 +5,17 @@ section.course-index { div#accordion { h3 { + @include box-shadow(inset 0 1px 0 0 #eee); + border-top: 1px solid #d3d3d3; overflow: hidden; margin: 0; - &:last-child { - @include box-shadow(none); + &:first-child { + border: none; + } + + &:hover { + @include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(225,225,225))); } &.ui-accordion-header { @@ -20,14 +26,10 @@ section.course-index { color: lighten($text-color, 10%); } - &.ui-state-hover { - border: none; - border-bottom: 1px solid #d3d3d3; - } - &.ui-state-active { @include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(225,225,225))); @extend .active; + border-bottom: 1px solid #d3d3d3; } } } @@ -37,7 +39,6 @@ section.course-index { @include box-shadow(inset -1px 0 0 #e6e6e6); background: #dadada; border: none; - border-bottom: 1px solid #c3c3c3; font-size: 12px; margin: 0; padding: 1em 1.5em; diff --git a/sass/plugins/_jquery-ui-1.8.16.custom.scss b/sass/plugins/_jquery-ui-1.8.16.custom.scss index d03e1ca7a2..43847b6441 100644 --- a/sass/plugins/_jquery-ui-1.8.16.custom.scss +++ b/sass/plugins/_jquery-ui-1.8.16.custom.scss @@ -333,7 +333,7 @@ .ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } .ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } .ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } -.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } .ui-accordion .ui-accordion-content-active { display: block; } /* * jQuery UI Autocomplete 1.8.16 @@ -565,4 +565,4 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad * http://docs.jquery.com/UI/Progressbar#theming */ .ui-progressbar { height:2em; text-align: left; } -.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } diff --git a/sass/wiki/_sidebar.scss b/sass/wiki/_sidebar.scss index a4ebfe628d..90bc654e08 100644 --- a/sass/wiki/_sidebar.scss +++ b/sass/wiki/_sidebar.scss @@ -10,27 +10,35 @@ div#wiki_panel { } input[type="button"] { - @extend h3; - color: lighten($text-color, 10%); - font-size: $body-font-size; - margin: 0 !important; + background: transparent; + border: none; + @include box-shadow(none); + color: #666; + font-size: 14px; + font-weight: bold; + margin: 0px; padding: 7px lh(); text-align: left; @include transition(); width: 100%; - - &:hover { - @include box-shadow(0 1px 0 #fff); - background: #efefef; - } } ul { li { + @include box-shadow(inset 0 1px 0 0 #eee); + border-top: 1px solid #d3d3d3; + + &:hover { + background: #efefef; + @include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(225,225,225))); + } + + &:first-child { + border: none; + } + &.search { - border-bottom: 1px solid #d3d3d3; - @include box-shadow(0 1px 0 #eee); - padding: 7px lh(); + padding: 10px lh(); label { display: none; @@ -39,18 +47,21 @@ div#wiki_panel { &.create-article { h3 { - a { - padding: 7px lh(); - } } } + + a { + color: #666; + font-size: 14px; + padding: 7px lh(); + } } } div#wiki_create_form { @extend .clearfix; - background: #d6d6d6; - border-bottom: 1px solid #bbb; + background: #dadada; + border-bottom: 1px solid #d3d3d3; padding: 15px; input[type="text"] { diff --git a/settings.py b/settings.py index 2be6be485a..9a11c42ae6 100644 --- a/settings.py +++ b/settings.py @@ -133,6 +133,7 @@ TEMPLATE_LOADERS = ( MIDDLEWARE_CLASSES = ( 'util.middleware.ExceptionLoggingMiddleware', + 'util.middleware.AcceptMiddleware', 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/templates/accordion_bak.html b/templates/accordion_bak.html deleted file mode 100644 index 9ac10a5764..0000000000 --- a/templates/accordion_bak.html +++ /dev/null @@ -1,70 +0,0 @@ -
We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible. Please email us at technical@mitx.mit.edu to report any problems or downtime.
+We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible. Please email us at technical@mitx.mit.edu to report any problems or downtime.
+