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..e9ec4518c8 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -3,6 +3,7 @@ import time import logging import requests from lxml import etree +from path import path # NOTE (THK): Only used for detecting presence of syllabus from xmodule.util.decorators import lazyproperty from xmodule.graders import load_grading_policy @@ -60,6 +61,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: @@ -75,6 +78,10 @@ class CourseDescriptor(SequenceDescriptor): # NOTE: relies on the modulestore to call set_grading_policy() right after # init. (Modulestore is in charge of figuring out where to load the policy from) + # NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically + # disable the syllabus content for courses that do not provide a syllabus + self.syllabus_present = self.system.resources_fs.exists(path('syllabus')) + def set_grading_policy(self, policy_str): """Parse the policy specified in policy_str, and save it""" @@ -94,8 +101,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 +215,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 +236,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/css/sequence/display.scss b/common/lib/xmodule/xmodule/css/sequence/display.scss index 8c3ab322e2..05002e881d 100644 --- a/common/lib/xmodule/xmodule/css/sequence/display.scss +++ b/common/lib/xmodule/xmodule/css/sequence/display.scss @@ -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,49 @@ 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%; + outline: 0; - &:hover { - background-repeat: no-repeat; - background-position: center; - background-color: #F3F3F3; + &:focus { + outline: 0; } - &.visited { - background-color: #F3F3F3; - - &:hover { - background-position: center center; - } + &:hover { + background-color: #fff; + background-repeat: no-repeat; + 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 +214,24 @@ nav.sequence-nav { } ul { - list-style: none; - height: 100%; position: absolute; + top: 0; + list-style: none !important; + height: 100%; 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 +239,18 @@ 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); + outline: 0; + + &:focus { + outline: 0; + } &:hover { opacity: .5; - background-color: #f4f4f4; } &.disabled { @@ -210,15 +261,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; } } } @@ -296,3 +355,10 @@ nav.sequence-bottom { } } } + +div.course-wrapper section.course-content ol.vert-mod > li ul.sequence-nav-buttons { + list-style: none !important; +} + + + diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index 848294b699..504d5df8cf 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -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; @@ -102,6 +101,11 @@ div.video { @include transition(background-color, opacity); width: 14px; background: url('../images/vcr.png') 15px 15px no-repeat; + outline: 0; + + &:focus { + outline: 0; + } &:empty { height: 46px; @@ -172,6 +176,11 @@ div.video { @include transition(); -webkit-font-smoothing: antialiased; width: 116px; + outline: 0; + + &:focus { + outline: 0; + } h3 { color: #999; @@ -401,6 +410,7 @@ div.video { overflow: auto; width: flex-grid(3, 9); margin: 0; + font-size: 14px; li { border: 0; 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/common/static/images/sequence-nav/next-icon.png b/common/static/images/sequence-nav/next-icon.png index ece431dcb8..b54f8566de 100644 Binary files a/common/static/images/sequence-nav/next-icon.png and b/common/static/images/sequence-nav/next-icon.png differ diff --git a/common/static/images/sequence-nav/previous-icon.png b/common/static/images/sequence-nav/previous-icon.png index 3b995df1f0..33277d6af4 100644 Binary files a/common/static/images/sequence-nav/previous-icon.png and b/common/static/images/sequence-nav/previous-icon.png differ diff --git a/common/static/js/vendor/backbone-min.js b/common/static/js/vendor/backbone-min.js index c1c0d4fff2..91f29ffafb 100644 --- a/common/static/js/vendor/backbone-min.js +++ b/common/static/js/vendor/backbone-min.js @@ -4,35 +4,37 @@ // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org -(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks= -{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g= -z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent= -{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null== -b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent: -b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)}; -a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error, -h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t(); -return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending= -{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length|| -!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator); -this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c=b))this.iframe=i(' - + {% endblock %} diff --git a/lms/templates/wiki/includes/article_menu.html b/lms/templates/wiki/includes/article_menu.html index 5088ae9570..0d505ccebd 100644 --- a/lms/templates/wiki/includes/article_menu.html +++ b/lms/templates/wiki/includes/article_menu.html @@ -3,7 +3,7 @@
  • - + View
  • @@ -11,7 +11,7 @@ %if article.can_write(user):
  • - + Edit
  • @@ -19,7 +19,7 @@
  • - + Changes
  • @@ -28,19 +28,25 @@ %if hasattr(plugin, "article_tab"):
  • - + ${plugin.article_tab[0]}
  • %endif %endfor + +<%doc> +The settings link has been disabled because the notifications app hasn't been integrated yet and those are the only useful settings. + %if not user.is_anonymous():
  • - + Settings
  • %endif + + diff --git a/lms/templates/wiki/plugins/attachments/index.html b/lms/templates/wiki/plugins/attachments/index.html index d448392933..464a63e5fe 100644 --- a/lms/templates/wiki/plugins/attachments/index.html +++ b/lms/templates/wiki/plugins/attachments/index.html @@ -115,7 +115,13 @@ {% if attachment.current_revision.user %}{{ attachment.current_revision.user }}{% else %}{% if user|is_moderator %}{{ attachment.current_revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %} {{ attachment.current_revision.get_size|filesizeformat }} - {{ attachment.attachmentrevision_set.all.count }} {% trans "revisions" %} + + + + + {% trans "File history" %} ({{ attachment.attachmentrevision_set.all.count }} {% trans "revisions" %}) + + diff --git a/lms/templates/wiki/preview_inline.html b/lms/templates/wiki/preview_inline.html index 9248454137..a5c6668d16 100644 --- a/lms/templates/wiki/preview_inline.html +++ b/lms/templates/wiki/preview_inline.html @@ -10,22 +10,31 @@
    {% if revision %}
    - {% trans "Previewing revision" %}: {{ revision.created }} (#{{ revision.revision_number }}) by {% if revision.user %}{{ revision.user }}{% else %}{% if user|is_moderator %}{{ revision.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %} + {% trans "Previewing revision" %}: + {% include "wiki/includes/revision_info.html" %}
    {% endif %} {% if merge %}
    {% trans "Previewing merge between" %}: - {{ merge1.created }} (#{{ merge1.revision_number }}) by {% if merge1.user %}{{ merge1.user }}{% else %}{% if user|is_moderator %}{{ merge1.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %} + {% include "wiki/includes/revision_info.html" with revision=merge1 %} {% trans "and" %} - {{ merge1.created }} (#{{ merge1.revision_number }}) by {% if merge1.user %}{{ merge1.user }}{% else %}{% if user|is_moderator %}{{ merge1.ip_address|default:"anonymous (IP not logged)" }}{% else %}{% trans "anonymous (IP logged)" %}{% endif %}{% endif %} + {% include "wiki/includes/revision_info.html" with revision=merge2 %}
    {% endif %}

    {{ title }}

    - {% wiki_render article content %} + {% if revision and revision.deleted %} +
    + This revision has been deleted. +

    Restoring to this revision will mark the article as deleted.

    +
    + {% else %} + {% wiki_render article content %} + {% endif %} +
    diff --git a/lms/templates/wiki/settings.html b/lms/templates/wiki/settings.html deleted file mode 100644 index a932a26498..0000000000 --- a/lms/templates/wiki/settings.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends "wiki/base.html" %} -{% load wiki_tags i18n %} -{% load url from future %} - -{% block pagetitle %}{% trans "Settings" %}: {{ article.current_revision.title }}{% endblock %} - -{% block wiki_breadcrumbs %} - {% include "wiki/includes/breadcrumbs.html" %} -{% endblock %} - -{% block wiki_contents %} - -
    -
    - {% if selected_tab != "edit" %} -

    {{ article.current_revision.title }}

    - {% endif %} - - {% for form in forms %} -
    -

    {{ form.settings_form_headline }}

    -
    - {% wiki_form form %} -
    -
    - -
    -
    - {% endfor %} -
    - - - - -
    -
    - {% trans "Last modified:" %}
    - {{ article.current_revision.modified }} -
    - -
    -
    - -{% endblock %} - diff --git a/lms/templates/wiki/wiki-404.html b/lms/templates/wiki/wiki-404.html deleted file mode 100644 index a18ea3786a..0000000000 --- a/lms/templates/wiki/wiki-404.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "wiki/base.html" %} -{% load wiki_tags i18n %} -{% load url from future %} - -{% block pagetitle %}{{ article.current_revision.title }}{% endblock %} - -{% block wiki_breadcrumbs %} -{% include "wiki/includes/breadcrumbs.html" %} -{% endblock %} - -{% block wiki_contents %} - -
    -

    This article was not found, and neither was the parent. Go back to the main wiki article.

    - -
    - -{% endblock %} diff --git a/lms/urls.py b/lms/urls.py index 6e6ad4300e..ca5b01fa2c 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -10,7 +10,7 @@ if settings.DEBUG: admin.autodiscover() urlpatterns = ('', - url(r'^$', 'student.views.index', name="root"), # Main marketing page, or redirect to courseware + url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware url(r'^dashboard$', 'student.views.dashboard', name="dashboard"), url(r'^admin_dashboard$', 'dashboard.views.dashboard'), @@ -115,7 +115,7 @@ if settings.COURSEWARE_ENABLED: # url(r'^edit_circuit/(?P[^/]*)$', 'circuit.views.edit_circuit'), # url(r'^save_circuit/(?P[^/]*)$', 'circuit.views.save_circuit'), - url(r'^courses/?$', 'courseware.views.courses', name="courses"), + url(r'^courses/?$', 'branding.views.courses', name="courses"), url(r'^change_enrollment$', 'student.views.change_enrollment_view', name="change_enrollment"), @@ -154,7 +154,8 @@ if settings.COURSEWARE_ENABLED: 'courseware.views.gradebook', name='gradebook'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/grade_summary$', 'courseware.views.grade_summary', name='grade_summary'), - + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/enroll_students$', + 'courseware.views.enroll_students', name='enroll_students'), ) # discussion forums live within courseware, so courseware must be enabled first diff --git a/rakefile b/rakefile index 9eaa4534f2..053abf56a8 100644 --- a/rakefile +++ b/rakefile @@ -200,6 +200,8 @@ task :package do "--exclude=**/.git/**", "--exclude=**/*.pyc", "--exclude=**/reports/**", + "--exclude=**/test_root/**", + "--exclude=**/.coverage/**", "-C", "#{REPO_ROOT}", "--provides=#{PACKAGE_NAME}", "--name=#{NORMALIZED_DEPLOY_NAME}", diff --git a/repo-requirements.txt b/repo-requirements.txt index cb016b5222..82c0b06b34 100644 --- a/repo-requirements.txt +++ b/repo-requirements.txt @@ -1,6 +1,6 @@ -e git://github.com/MITx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles -e git://github.com/MITx/django-pipeline.git#egg=django-pipeline --e git://github.com/benjaoming/django-wiki.git@02275fb4#egg=django-wiki +-e git://github.com/benjaoming/django-wiki.git@3576a2d#egg=django-wiki -e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev -e common/lib/capa -e common/lib/xmodule diff --git a/requirements.txt b/requirements.txt index c5cafe64b8..72b13e63ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ django>=1.4,<1.5 pip numpy scipy -markdown +Markdown<2.3.0 pygments lxml boto @@ -43,5 +43,7 @@ django-ses django-storages django-threaded-multihost django-sekizai<0.7 +django-mptt>=0.5.3 +sorl-thumbnail networkx -r repo-requirements.txt