From 18861e4ef9b23ca062aedde39f31412a0f7c0fdf Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 1 Jul 2013 17:06:32 -0400 Subject: [PATCH 001/137] Removed some pep8 violations. --- cms/djangoapps/contentstore/views/course.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index d790697612..252d06b3a5 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -12,13 +12,17 @@ from django.core.urlresolvers import reverse from mitxmako.shortcuts import render_to_response from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import ItemNotFoundError, \ - InvalidLocationError +from xmodule.modulestore.exceptions import ( + ItemNotFoundError, InvalidLocationError) from xmodule.modulestore import Location -from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update -from contentstore.utils import get_lms_link_for_item, add_extra_panel_tab, remove_extra_panel_tab -from models.settings.course_details import CourseDetails, CourseSettingsEncoder +from contentstore.course_info_model import ( + get_course_updates, update_course_updates, delete_course_update) +from contentstore.utils import ( + get_lms_link_for_item, add_extra_panel_tab, remove_extra_panel_tab) +from models.settings.course_details import ( + CourseDetails, CourseSettingsEncoder) + from models.settings.course_grading import CourseGradingModel from models.settings.course_metadata import CourseMetadata from auth.authz import create_all_course_groups, is_user_in_creator_group @@ -27,8 +31,9 @@ from util.json_request import expect_json from .access import has_access, get_location_and_verify_access from .requests import get_request_method from .tabs import initialize_course_tabs -from .component import OPEN_ENDED_COMPONENT_TYPES, \ - NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY +from .component import ( + OPEN_ENDED_COMPONENT_TYPES, NOTE_COMPONENT_TYPES, + ADVANCED_COMPONENT_POLICY_KEY) from django_comment_common.utils import seed_permissions_roles import datetime From 7d7eee7d0d5585d24742fb4857ef5bbe4439d222 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Wed, 8 May 2013 12:10:25 -0400 Subject: [PATCH 002/137] inital work to use streams/chunks when handling uploaded assets --- common/lib/xmodule/xmodule/contentstore/content.py | 7 +++++-- common/lib/xmodule/xmodule/contentstore/mongo.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/contentstore/content.py b/common/lib/xmodule/xmodule/contentstore/content.py index 64069ce9fa..b0d8c357a3 100644 --- a/common/lib/xmodule/xmodule/contentstore/content.py +++ b/common/lib/xmodule/xmodule/contentstore/content.py @@ -113,7 +113,7 @@ class ContentStore(object): ''' raise NotImplementedError - def generate_thumbnail(self, content): + def generate_thumbnail(self, content, tempfile_path=None): thumbnail_content = None # use a naming convention to associate originals with the thumbnail thumbnail_name = StaticContent.generate_thumbnail_name(content.location.name) @@ -129,7 +129,10 @@ class ContentStore(object): # My understanding is that PIL will maintain aspect ratios while restricting # the max-height/width to be whatever you pass in as 'size' # @todo: move the thumbnail size to a configuration setting?!? - im = Image.open(StringIO.StringIO(content.data)) + if tempfile_path is None: + im = Image.open(StringIO.StringIO(content.data)) + else: + im = Image.open(tempfile_path) # I've seen some exceptions from the PIL library when trying to save palletted # PNG files to JPEG. Per the google-universe, they suggest converting to RGB first. diff --git a/common/lib/xmodule/xmodule/contentstore/mongo.py b/common/lib/xmodule/xmodule/contentstore/mongo.py index ce75adc1ee..8cb4143fd9 100644 --- a/common/lib/xmodule/xmodule/contentstore/mongo.py +++ b/common/lib/xmodule/xmodule/contentstore/mongo.py @@ -35,8 +35,11 @@ class MongoContentStore(ContentStore): with self.fs.new_file(_id=id, filename=content.get_url_path(), content_type=content.content_type, displayname=content.name, thumbnail_location=content.thumbnail_location, import_path=content.import_path) as fp: - - fp.write(content.data) + if hasattr(content.data, '__iter__'): + for chunk in content.data: + fp.write(chunk) + else: + fp.write(content.data) return content From 1d7e15fc2ec38738dc1cfef95b44cd8b2b821c1a Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Mon, 20 May 2013 15:08:17 -0400 Subject: [PATCH 003/137] Remove duplicate URL route --- cms/urls.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cms/urls.py b/cms/urls.py index 1bd9850d4b..11e7058a47 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -94,9 +94,6 @@ urlpatterns = ('', # nopep8 url(r'^not_found$', 'contentstore.views.not_found', name='not_found'), url(r'^server_error$', 'contentstore.views.server_error', name='server_error'), - url(r'^(?P[^/]+)/(?P[^/]+)/assets/(?P[^/]+)$', - 'contentstore.views.asset_index', name='asset_index'), - # temporary landing page for edge url(r'^edge$', 'contentstore.views.edge', name='edge'), # noop to squelch ajax errors From 6642cdddae80aefcd275bc90e54a854b0d4551e3 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 20 May 2013 15:37:02 -0400 Subject: [PATCH 004/137] support uploading and referencing assets as streams rather than having to read everything into memory first --- cms/djangoapps/contentstore/views/assets.py | 37 +++++++++++++----- common/djangoapps/contentserver/middleware.py | 17 +++++--- .../xmodule/xmodule/contentstore/content.py | 39 ++++++++++++++++++- .../lib/xmodule/xmodule/contentstore/mongo.py | 36 +++++++++++++---- 4 files changed, 104 insertions(+), 25 deletions(-) diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py index c85570fede..cf684ab3ba 100644 --- a/cms/djangoapps/contentstore/views/assets.py +++ b/cms/djangoapps/contentstore/views/assets.py @@ -89,11 +89,11 @@ def asset_index(request, org, course, name): }) -@login_required @ensure_csrf_cookie +@login_required def upload_asset(request, org, course, coursename): ''' - cdodge: this method allows for POST uploading of files into the course asset library, which will + This method allows for POST uploading of files into the course asset library, which will be supported by GridFS in MongoDB. ''' if request.method != 'POST': @@ -118,16 +118,25 @@ def upload_asset(request, org, course, coursename): # compute a 'filename' which is similar to the location formatting, we're using the 'filename' # nomenclature since we're using a FileSystem paradigm here. We're just imposing # the Location string formatting expectations to keep things a bit more consistent - - filename = request.FILES['file'].name - mime_type = request.FILES['file'].content_type - filedata = request.FILES['file'].read() + upload_file = request.FILES['file'] + filename = upload_file.name + mime_type = upload_file.content_type content_loc = StaticContent.compute_location(org, course, filename) - content = StaticContent(content_loc, filename, mime_type, filedata) + + chunked = upload_file.multiple_chunks() + if chunked: + content = StaticContent(content_loc, filename, mime_type, upload_file.chunks()) + else: + content = StaticContent(content_loc, filename, mime_type, upload_file.read()) + + thumbnail_content = None + thumbnail_location = None # first let's see if a thumbnail can be created - (thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(content) + (thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(content, + tempfile_path=None if not chunked else + upload_file.temporary_file_path()) # delete cached thumbnail even if one couldn't be created this time (else the old thumbnail will continue to show) del_cached_content(thumbnail_location) @@ -208,7 +217,9 @@ def remove_asset(request, org, course, name): @ensure_csrf_cookie @login_required def import_course(request, org, course, name): - + """ + This method will handle a POST request to upload and import a .tar.gz file into a specified course + """ location = get_location_and_verify_access(request, org, course, name) if request.method == 'POST': @@ -282,6 +293,10 @@ def import_course(request, org, course, name): @ensure_csrf_cookie @login_required def generate_export_course(request, org, course, name): + """ + This method will serialize out a course to a .tar.gz file which contains a XML-based representation of + the course + """ location = get_location_and_verify_access(request, org, course, name) loc = Location(location) @@ -312,7 +327,9 @@ def generate_export_course(request, org, course, name): @ensure_csrf_cookie @login_required def export_course(request, org, course, name): - + """ + This method serves up the 'Export Course' page + """ location = get_location_and_verify_access(request, org, course, name) course_module = modulestore().get_item(location) diff --git a/common/djangoapps/contentserver/middleware.py b/common/djangoapps/contentserver/middleware.py index 7deb0901aa..1f5e65b910 100644 --- a/common/djangoapps/contentserver/middleware.py +++ b/common/djangoapps/contentserver/middleware.py @@ -6,6 +6,7 @@ from xmodule.modulestore import InvalidLocationError from cache_toolbox.core import get_cached_content, set_cached_content from xmodule.exceptions import NotFoundError +import logging class StaticContentServer(object): def process_request(self, request): @@ -24,17 +25,21 @@ class StaticContentServer(object): if content is None: # nope, not in cache, let's fetch from DB try: - content = contentstore().find(loc) + content = contentstore().find(loc, as_stream=True) except NotFoundError: response = HttpResponse() response.status_code = 404 return response - # since we fetched it from DB, let's cache it going forward - set_cached_content(content) + # since we fetched it from DB, let's cache it going forward, but only if it's < 1MB + # this is because I haven't been able to find a means to stream data out of memcached + if content.length is not None: + if content.length < 1048576: + # since we've queried as a stream, let's read in the stream into memory to set in cache + content = content.copy_to_in_mem() + set_cached_content(content) else: - # @todo: we probably want to have 'cache hit' counters so we can - # measure the efficacy of our caches + # NOP here, but we may wish to add a "cache-hit" counter in the future pass # see if the last-modified at hasn't changed, if not return a 302 (Not Modified) @@ -50,7 +55,7 @@ class StaticContentServer(object): if if_modified_since == last_modified_at_str: return HttpResponseNotModified() - response = HttpResponse(content.data, content_type=content.content_type) + response = HttpResponse(content.stream_data(), content_type=content.content_type) response['Last-Modified'] = last_modified_at_str return response diff --git a/common/lib/xmodule/xmodule/contentstore/content.py b/common/lib/xmodule/xmodule/contentstore/content.py index b0d8c357a3..28a78ea8c1 100644 --- a/common/lib/xmodule/xmodule/contentstore/content.py +++ b/common/lib/xmodule/xmodule/contentstore/content.py @@ -14,11 +14,13 @@ from PIL import Image class StaticContent(object): - def __init__(self, loc, name, content_type, data, last_modified_at=None, thumbnail_location=None, import_path=None): + def __init__(self, loc, name, content_type, data, last_modified_at=None, thumbnail_location=None, import_path=None, + length=None): self.location = loc self.name = name # a display string which can be edited, and thus not part of the location which needs to be fixed self.content_type = content_type - self.data = data + self._data = data + self.length = length self.last_modified_at = last_modified_at self.thumbnail_location = Location(thumbnail_location) if thumbnail_location is not None else None # optional information about where this file was imported from. This is needed to support import/export @@ -45,6 +47,10 @@ class StaticContent(object): def get_url_path(self): return StaticContent.get_url_path_from_location(self.location) + @property + def data(self): + return self._data + @staticmethod def get_url_path_from_location(location): if location is not None: @@ -80,6 +86,35 @@ class StaticContent(object): loc = StaticContent.compute_location(course_namespace.org, course_namespace.course, path) return StaticContent.get_url_path_from_location(loc) + def stream_data(self): + yield self._data + + +class StaticContentStream(StaticContent): + def __init__(self, loc, name, content_type, stream, last_modified_at=None, thumbnail_location=None, import_path=None, + length=None): + super(StaticContentStream, self).__init__(loc, name, content_type, None, last_modified_at=last_modified_at, + thumbnail_location=thumbnail_location, import_path=import_path, + length=length) + self._stream = stream + + def stream_data(self): + while True: + chunk = self._stream.read(1024) + if len(chunk) == 0: + break + yield chunk + + def close(self): + self._stream.close() + + def copy_to_in_mem(self): + self._stream.seek(0) + content = StaticContent(self.location, self.name, self.content_type, self._stream.read(), + last_modified_at=self.last_modified_at, thumbnail_location=self.thumbnail_location, + import_path=self.import_path, length=self.length) + return content + class ContentStore(object): ''' diff --git a/common/lib/xmodule/xmodule/contentstore/mongo.py b/common/lib/xmodule/xmodule/contentstore/mongo.py index 8cb4143fd9..482ce585e6 100644 --- a/common/lib/xmodule/xmodule/contentstore/mongo.py +++ b/common/lib/xmodule/xmodule/contentstore/mongo.py @@ -8,7 +8,7 @@ from xmodule.contentstore.content import XASSET_LOCATION_TAG import logging -from .content import StaticContent, ContentStore +from .content import StaticContent, ContentStore, StaticContentStream from xmodule.exceptions import NotFoundError from fs.osfs import OSFS import os @@ -47,20 +47,42 @@ class MongoContentStore(ContentStore): if self.fs.exists({"_id": id}): self.fs.delete(id) - def find(self, location, throw_on_not_found=True): + def find(self, location, throw_on_not_found=True, as_stream=False): id = StaticContent.get_id_from_location(location) try: - with self.fs.get(id) as fp: - return StaticContent(location, fp.displayname, fp.content_type, fp.read(), - fp.uploadDate, - thumbnail_location=fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None, - import_path=fp.import_path if hasattr(fp, 'import_path') else None) + if as_stream: + fp = self.fs.get(id) + return StaticContentStream(location, fp.displayname, fp.content_type, fp, last_modified_at=fp.uploadDate, + thumbnail_location=fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None, + import_path=fp.import_path if hasattr(fp, 'import_path') else None, + length=fp.length) + else: + with self.fs.get(id) as fp: + return StaticContent(location, fp.displayname, fp.content_type, fp.read(), last_modified_at=fp.uploadDate, + thumbnail_location=fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None, + import_path=fp.import_path if hasattr(fp, 'import_path') else None, + length=fp.length) except NoFile: if throw_on_not_found: raise NotFoundError() else: return None + def get_stream(self, location): + id = StaticContent.get_id_from_location(location) + try: + handle = self.fs.get(id) + except NoFile: + raise NotFoundError() + + return handle + + def close_stream(self, handle): + try: + handle.close() + except: + pass + def export(self, location, output_directory): content = self.find(location) From cc4e01b4c6af25d95a61d0fdc15dd1b78f758f89 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Mon, 20 May 2013 15:46:16 -0400 Subject: [PATCH 005/137] Removed obsolete active_tab template variable --- cms/djangoapps/contentstore/views/assets.py | 3 --- cms/djangoapps/contentstore/views/component.py | 1 - cms/djangoapps/contentstore/views/course.py | 2 -- cms/djangoapps/contentstore/views/tabs.py | 2 -- cms/djangoapps/contentstore/views/user.py | 1 - 5 files changed, 9 deletions(-) diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py index cf684ab3ba..931693de29 100644 --- a/cms/djangoapps/contentstore/views/assets.py +++ b/cms/djangoapps/contentstore/views/assets.py @@ -77,7 +77,6 @@ def asset_index(request, org, course, name): asset_display.append(display_info) return render_to_response('asset_index.html', { - 'active_tab': 'assets', 'context_course': course_module, 'assets': asset_display, 'upload_asset_callback_url': upload_asset_callback_url, @@ -285,7 +284,6 @@ def import_course(request, org, course, name): return render_to_response('import.html', { 'context_course': course_module, - 'active_tab': 'import', 'successful_import_redirect_url': get_url_reverse('CourseOutline', course_module) }) @@ -336,6 +334,5 @@ def export_course(request, org, course, name): return render_to_response('export.html', { 'context_course': course_module, - 'active_tab': 'export', 'successful_import_redirect_url': '' }) diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 4377943b36..6f5938f64f 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -209,7 +209,6 @@ def edit_unit(request, location): return render_to_response('unit.html', { 'context_course': course, - 'active_tab': 'courseware', 'unit': item, 'unit_location': location, 'components': components, diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 252d06b3a5..ea941de168 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -69,7 +69,6 @@ def course_index(request, org, course, name): sections = course.get_children() return render_to_response('overview.html', { - 'active_tab': 'courseware', 'context_course': course, 'lms_link': lms_link, 'sections': sections, @@ -161,7 +160,6 @@ def course_info(request, org, course, name, provided_id=None): location = Location(['i4x', org, course, 'course_info', "updates"]) return render_to_response('course_info.html', { - 'active_tab': 'courseinfo-tab', 'context_course': course_module, 'url_base': "/" + org + "/" + course + "/", 'course_updates': json.dumps(get_course_updates(location)), diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py index f9349a5ca0..38a8f35ea1 100644 --- a/cms/djangoapps/contentstore/views/tabs.py +++ b/cms/djangoapps/contentstore/views/tabs.py @@ -108,7 +108,6 @@ def edit_tabs(request, org, course, coursename): ] return render_to_response('edit-tabs.html', { - 'active_tab': 'pages', 'context_course': course_item, 'components': components }) @@ -123,7 +122,6 @@ def static_pages(request, org, course, coursename): course = modulestore().get_item(location) return render_to_response('static-pages.html', { - 'active_tab': 'pages', 'context_course': course, }) diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py index 462b184718..40334cb3e8 100644 --- a/cms/djangoapps/contentstore/views/user.py +++ b/cms/djangoapps/contentstore/views/user.py @@ -73,7 +73,6 @@ def manage_users(request, location): course_module = modulestore().get_item(location) return render_to_response('manage_users.html', { - 'active_tab': 'users', 'context_course': course_module, 'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME), 'add_user_postback_url': reverse('add_user', args=[location]).rstrip('/'), From 3d61c6294c6fabe2825c6c24b00483926c40569d Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Mon, 20 May 2013 15:54:13 -0400 Subject: [PATCH 006/137] Stubbed out textbooks index page --- cms/djangoapps/contentstore/views/course.py | 17 ++++++++++++++++- cms/static/sass/elements/_header.scss | 1 + cms/templates/textbooks.html | 21 +++++++++++++++++++++ cms/templates/widgets/header.html | 3 +++ cms/urls.py | 2 ++ 5 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 cms/templates/textbooks.html diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index ea941de168..8cfe0dac60 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -44,7 +44,7 @@ __all__ = ['course_index', 'create_new_course', 'course_info', 'course_config_advanced_page', 'course_settings_updates', 'course_grader_updates', - 'course_advanced_updates'] + 'course_advanced_updates', 'textbook_index'] @login_required @@ -411,3 +411,18 @@ def course_advanced_updates(request, org, course, name): return HttpResponseBadRequest("Incorrect setting format. " + str(e), content_type="text/plain") return HttpResponse(response_json, mimetype="application/json") + + +@login_required +@ensure_csrf_cookie +def textbook_index(request, org, course, name): + """ + Display an editable textbook overview. + + org, course, name: Attributes of the Location for the item to edit + """ + location = get_location_and_verify_access(request, org, course, name) + course = modulestore().get_item(location, depth=3) + return render_to_response('textbooks.html', { + 'context_course': course, + }) diff --git a/cms/static/sass/elements/_header.scss b/cms/static/sass/elements/_header.scss index 026ca96274..7f6c207eb8 100644 --- a/cms/static/sass/elements/_header.scss +++ b/cms/static/sass/elements/_header.scss @@ -349,6 +349,7 @@ body.course.outline .nav-course-courseware-outline, body.course.updates .nav-course-courseware-updates, body.course.pages .nav-course-courseware-pages, body.course.uploads .nav-course-courseware-uploads, +body.course.textbooks .nav-course-courseware-textbooks, // course settings body.course.schedule .nav-course-settings .title, diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html new file mode 100644 index 0000000000..d12d00131d --- /dev/null +++ b/cms/templates/textbooks.html @@ -0,0 +1,21 @@ +<%inherit file="base.html" /> + +<%block name="title">Textbooks +<%block name="bodyclass">is-signedin course textbooks + +<%block name="content"> +
+
+

+ Course Content + > Textbooks +

+
+
+ +
+
+ I'm a stub! +
+
+ diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index a699b976cf..877e8aaa07 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -37,6 +37,9 @@ + diff --git a/cms/urls.py b/cms/urls.py index 11e7058a47..d29e355743 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -81,6 +81,8 @@ urlpatterns = ('', # nopep8 'contentstore.views.asset_index', name='asset_index'), url(r'^(?P[^/]+)/(?P[^/]+)/assets/(?P[^/]+)/remove$', 'contentstore.views.assets.remove_asset', name='remove_asset'), + url(r'^(?P[^/]+)/(?P[^/]+)/textbooks/(?P[^/]+)$', + 'contentstore.views.textbook_index', name='textbook_index'), # this is a generic method to return the data/metadata associated with a xmodule url(r'^module_info/(?P.*)$', From 6472051adc9b60615d0c742e66fbf3ae0a0e8b22 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Mon, 20 May 2013 16:08:12 -0400 Subject: [PATCH 007/137] Increased the awesomeness of the textbook filler page by a factor of 12.8 --- cms/templates/textbooks.html | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index d12d00131d..d3ac90e6fa 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -10,12 +10,28 @@ Course Content > Textbooks + +
- I'm a stub! + Textbooks are happy. Textbooks are cool. This is the filler text + explaining why you should use a feature as happy and as cool as textbooks. + If you use textbooks, you will make friends and influence people. You will + lose weight and people will compliment you on your appearance. Children + around the world will sing praises to your name, and celebrities will + recongize you as a totally hip and groovy person. If you don't use textbooks, + your life will be forever incomplete, and you will die alone and unloved. + Make the right choice. Upload your first textbook now.
From ace8914a53a0a22a0a0f916d1dd9146cb9aa4f20 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 21 May 2013 10:27:09 -0400 Subject: [PATCH 008/137] Set up Backbone models/views to construct logic of adding textbooks/chapters --- cms/static/coffee/src/main.coffee | 1 + cms/templates/textbooks.html | 135 ++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/cms/static/coffee/src/main.coffee b/cms/static/coffee/src/main.coffee index 863d21d846..1ce1d5d3eb 100644 --- a/cms/static/coffee/src/main.coffee +++ b/cms/static/coffee/src/main.coffee @@ -3,6 +3,7 @@ AjaxPrefix.addAjaxPrefix(jQuery, -> CMS.prefix) @CMS = Models: {} Views: {} + Collections: {} prefix: $("meta[name='path_prefix']").attr('content') diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index d3ac90e6fa..5e99c1b579 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -3,6 +3,141 @@ <%block name="title">Textbooks <%block name="bodyclass">is-signedin course textbooks +<%block name="header_extras"> +<%text> + + + + + + +<%block name="jsextra"> + + + <%block name="content">
From a84f66cd51ed68fed253c09eab29f46f2e6028d7 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 21 May 2013 17:32:48 -0400 Subject: [PATCH 009/137] Pass asset upload URL to Javascript --- cms/djangoapps/contentstore/views/course.py | 6 ++++++ cms/templates/textbooks.html | 2 ++ 2 files changed, 8 insertions(+) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 8cfe0dac60..0b5e422056 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -423,6 +423,12 @@ def textbook_index(request, org, course, name): """ location = get_location_and_verify_access(request, org, course, name) course = modulestore().get_item(location, depth=3) + upload_asset_callback_url = reverse('upload_asset', kwargs={ + 'org': org, + 'course': course, + 'coursename': name + }) return render_to_response('textbooks.html', { 'context_course': course, + 'upload_asset_callback_url': upload_asset_callback_url, }) diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index 5e99c1b579..08686de718 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -43,6 +43,8 @@ <%block name="jsextra"> @@ -116,8 +116,7 @@ CMS.Views.ChapterEdit = Backbone.View.extend({ this.$el.html(this.template({ name: this.model.escape('name'), asset_path: this.model.escape('asset_path'), - order: this.model.get('order'), - close: true + order: this.model.get('order') })); return this; }, From 2d13325bafcaf0b560569bff73bc139495014c9c Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Thu, 23 May 2013 10:54:11 -0400 Subject: [PATCH 011/137] Move underscore templates out into separate files --- cms/templates/js/chapter.underscore | 14 ++++++++++++ cms/templates/js/textbook.underscore | 14 ++++++++++++ cms/templates/textbooks.html | 34 +++------------------------- 3 files changed, 31 insertions(+), 31 deletions(-) create mode 100644 cms/templates/js/chapter.underscore create mode 100644 cms/templates/js/textbook.underscore diff --git a/cms/templates/js/chapter.underscore b/cms/templates/js/chapter.underscore new file mode 100644 index 0000000000..765a558b22 --- /dev/null +++ b/cms/templates/js/chapter.underscore @@ -0,0 +1,14 @@ +
  • +
    + + " value="<%= name %>"> +

    <%= gettext("the title/name of the chapter that will be used in navigating") %>

    +
    +
    + + " value="<%= asset_path %>"> +

    <%= gettext("provide the path for a file or asset already added to this course") %>

    +
    + "> + close-button +
  • diff --git a/cms/templates/js/textbook.underscore b/cms/templates/js/textbook.underscore new file mode 100644 index 0000000000..9b43045d48 --- /dev/null +++ b/cms/templates/js/textbook.underscore @@ -0,0 +1,14 @@ +
    +
    + + value="<%= name %>"> +

    <%= gettext("the title/name of the text book as you would like your students to see it.") %> +

    +
      +

      <%= gettext("Note: It's best practice to break your course's textbook into multiple chapters to reduce loading times for students. Breaking up textbooks into chapters can also help with students more easily finding a concept or topic-based information.") %>

      + +
      + " /> + " /> +
      +
      diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index d4aeb3ac5d..a53ae35a91 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -1,44 +1,16 @@ <%inherit file="base.html" /> +<%namespace name='static' file='static_content.html'/> <%block name="title">Textbooks <%block name="bodyclass">is-signedin course textbooks <%block name="header_extras"> -<%text> - - <%block name="jsextra"> From 9d59a1313c02cea3979258c622efc5edddbb850f Mon Sep 17 00:00:00 2001 From: Frances Botsford Date: Thu, 23 May 2013 21:51:18 -0400 Subject: [PATCH 012/137] initial html and sass for textbook upload --- cms/static/sass/base-style.scss | 1 + cms/static/sass/elements/_controls.scss | 22 +++ cms/static/sass/views/_textbooks.scss | 203 ++++++++++++++++++++++++ cms/templates/js/chapter.underscore | 22 +-- cms/templates/js/textbook.underscore | 32 ++-- cms/templates/textbooks.html | 21 +-- common/static/sass/_mixins.scss | 18 +++ 7 files changed, 288 insertions(+), 31 deletions(-) create mode 100644 cms/static/sass/views/_textbooks.scss diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index ff8405db38..660ba23e8c 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -58,6 +58,7 @@ @import 'views/unit'; @import 'views/users'; @import 'views/checklists'; +@import 'views/textbooks'; // temp - inherited @import 'assets/content-types'; diff --git a/cms/static/sass/elements/_controls.scss b/cms/static/sass/elements/_controls.scss index 8c43934a44..2bddbbc0a0 100644 --- a/cms/static/sass/elements/_controls.scss +++ b/cms/static/sass/elements/_controls.scss @@ -133,6 +133,28 @@ } } +// blue small/inline button +.btn-flat-blue { + @extend .btn-flat; + color: $white; + background-color: $blue; + + &:hover, &:active { + background: $blue-l4; + color: $blue-s2; + } + + &.current, &.active { + border-color: $blue-l3; + background: $blue-l3; + color: $blue-d1; + + &:hover, &:active { + + } + } +} + // ==================== // simple dropdown button styling - should we move this elsewhere? diff --git a/cms/static/sass/views/_textbooks.scss b/cms/static/sass/views/_textbooks.scss new file mode 100644 index 0000000000..26f4b1c4e1 --- /dev/null +++ b/cms/static/sass/views/_textbooks.scss @@ -0,0 +1,203 @@ +// studio - views - textbooks +// ==================== + +body.course.textbooks { + + form { + @include box-sizing(border-box); + @include box-shadow(0 1px 2px $shadow-l1); + @include border-radius(2px); + width: 100%; + border: 1px solid $gray-l2; + padding: $baseline ($baseline*1.5); + background: $white; + + .actions { + margin-top: $baseline; + + .action-add-chapter { + @extend .btn-flat-blue; + @extend .t-action2; + @include transition(all .15s); + display: block; + width: 100%; + margin-bottom: ($baseline*1.5); + padding: ($baseline/5) $baseline; + font-weight: 600; + } + + .action-primary { + @include blue-button; + @extend .t-action2; + @include transition(all .15s); + display: inline-block; + padding: ($baseline/5) $baseline ($baseline/4) $baseline; + font-weight: 600; + text-transform: uppercase; + } + + .action-secondary { + @include grey-button; + @extend .t-action2; + @include transition(all .15s); + display: inline-block; + padding: ($baseline/5) $baseline ($baseline/4) $baseline; + font-weight: 600; + text-transform: uppercase; + } + + + } + + .list-input { + margin: 0; + padding: 0; + list-style: none; + + .field { + margin: 0 0 ($baseline*0.75) 0; + + &:last-child { + margin-bottom: 0; + } + + &.required { + + label { + font-weight: 600; + } + + label:after { + margin-left: ($baseline/4); + content: "*"; + } + } + + label, input, textarea { + display: block; + } + + label { + @extend .t-copy-sub1; + @include transition(color, 0.15s, ease-in-out); + margin: 0 0 ($baseline/4) 0; + + &.is-focused { + color: $blue; + } + } + + input, textarea { + @extend .t-copy-base; + height: 100%; + width: 100%; + padding: ($baseline/2); + + &.long { + width: 100%; + } + + &.short { + width: 25%; + } + + ::-webkit-input-placeholder { + color: $gray-l4; + } + + :-moz-placeholder { + color: $gray-l3; + } + + ::-moz-placeholder { + color: $gray-l3; + } + + :-ms-input-placeholder { + color: $gray-l3; + } + + &:focus { + + + .tip { + color: $gray; + } + } + } + + textarea.long { + height: ($baseline*5); + } + + input[type="checkbox"] { + display: inline-block; + margin-right: ($baseline/4); + width: auto; + height: auto; + + & + label { + display: inline-block; + } + } + + .tip { + @extend .t-copy-sub2; + @include transition(color, 0.15s, ease-in-out); + display: block; + margin-top: ($baseline/4); + color: $gray-l3; + } + } + + .field-group { + @include clearfix(); + margin: 0 0 ($baseline/2) 0; + + .field { + display: block; + width: 46%; + border-bottom: none; + margin: 0 $baseline 0 0; + padding: ($baseline/4) 0 0 0; + float: left; + position: relative; + + input, textarea { + width: 100%; + } + + .action-uploadasset { + @extend .t-action4; + @extend .btn-flat-blue; + @include transition(all .15s); + font-weight: 600; + text-align: center; + position: absolute; + top: 0; + right: 0; + padding: ($baseline/5) ($baseline/2) ($baseline/10) ($baseline/2); + } + + } + + .action-close { + @include white-button; + @include font-size(18); + margin-top: ($baseline*2); + border: 0; + padding: 0; + background: transparent; + + &:hover { + color: $blue; + background: transparent; + box-shadow: none; + } + } + + } + } + } + + +} diff --git a/cms/templates/js/chapter.underscore b/cms/templates/js/chapter.underscore index 765a558b22..9cdf4fd76f 100644 --- a/cms/templates/js/chapter.underscore +++ b/cms/templates/js/chapter.underscore @@ -1,14 +1,14 @@ -
    1. -
      - - " value="<%= name %>"> -

      <%= gettext("the title/name of the chapter that will be used in navigating") %>

      +
    2. +
      + + " value="<%= name %>" type="text"> + <%= gettext("the title/name of the chapter that will be used in navigating") %>
      -
      - - " value="<%= asset_path %>"> -

      <%= gettext("provide the path for a file or asset already added to this course") %>

      +
      + + " value="<%= asset_path %>" type="text"> + <%= gettext("provide the path for a file or asset already added to this course") %> +
      - "> - close-button + delete chapter
    3. diff --git a/cms/templates/js/textbook.underscore b/cms/templates/js/textbook.underscore index 9b43045d48..25dda42b26 100644 --- a/cms/templates/js/textbook.underscore +++ b/cms/templates/js/textbook.underscore @@ -1,14 +1,24 @@ -
      -
      - - value="<%= name %>"> -

      <%= gettext("the title/name of the text book as you would like your students to see it.") %> -

      -
        + +
        +
        + Required information to edit or add a textbook and chapters +
          +
        1. +
          + + " value="<%= name %>"> + <%= gettext("the title/name of the text book as you would like your students to see it.") %> +
          +
        2. +
        3. +
            +
          1. +

          <%= gettext("Note: It's best practice to break your course's textbook into multiple chapters to reduce loading times for students. Breaking up textbooks into chapters can also help with students more easily finding a concept or topic-based information.") %>

          - -
          - " /> - " /> +
          +
          + + +
          diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index a53ae35a91..774f9e26af 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -123,7 +123,7 @@ $(function() {

          Page Actions

          @@ -131,15 +131,18 @@ $(function() {
          +
          - Textbooks are happy. Textbooks are cool. This is the filler text - explaining why you should use a feature as happy and as cool as textbooks. - If you use textbooks, you will make friends and influence people. You will - lose weight and people will compliment you on your appearance. Children - around the world will sing praises to your name, and celebrities will - recongize you as a totally hip and groovy person. If you don't use textbooks, - your life will be forever incomplete, and you will die alone and unloved. - Make the right choice. Upload your first textbook now. +
          +
          diff --git a/common/static/sass/_mixins.scss b/common/static/sass/_mixins.scss index e5548aeaaa..f947d05f1e 100644 --- a/common/static/sass/_mixins.scss +++ b/common/static/sass/_mixins.scss @@ -196,3 +196,21 @@ padding: ($baseline*0.75); } +// small/inline button +.btn-flat { + @extend .btn; + @extend .btn-rounded; + border-width: 0; + border-radius: $baseline; + padding:($baseline/5) $baseline/2; + line-height: 1.3; + text-align: center; + + &:hover, &:active { + + } + + &.current, &.active { + + } +} From e0eec89e09c964854b5547592cdae8bff4971fe0 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Thu, 23 May 2013 17:05:22 -0400 Subject: [PATCH 013/137] Truncate AJAX error messages in notification --- cms/djangoapps/contentstore/views/course.py | 6 +- cms/static/coffee/src/main.coffee | 2 +- cms/templates/js/upload-dialog.underscore | 16 +++ cms/templates/textbooks.html | 108 +++++++++++++++++++- 4 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 cms/templates/js/upload-dialog.underscore diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 0b5e422056..6d45d5f62a 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -421,14 +421,16 @@ def textbook_index(request, org, course, name): org, course, name: Attributes of the Location for the item to edit """ - location = get_location_and_verify_access(request, org, course, name) - course = modulestore().get_item(location, depth=3) upload_asset_callback_url = reverse('upload_asset', kwargs={ 'org': org, 'course': course, 'coursename': name }) + + location = get_location_and_verify_access(request, org, course, name) + course = modulestore().get_item(location, depth=3) return render_to_response('textbooks.html', { 'context_course': course, + 'course': course, 'upload_asset_callback_url': upload_asset_callback_url, }) diff --git a/cms/static/coffee/src/main.coffee b/cms/static/coffee/src/main.coffee index 1ce1d5d3eb..da8cd400a9 100644 --- a/cms/static/coffee/src/main.coffee +++ b/cms/static/coffee/src/main.coffee @@ -18,7 +18,7 @@ $ -> $(document).ajaxError (event, jqXHR, ajaxSettings, thrownError) -> if ajaxSettings.notifyOnError is false - return + return if jqXHR.responseText try message = JSON.parse(jqXHR.responseText).error diff --git a/cms/templates/js/upload-dialog.underscore b/cms/templates/js/upload-dialog.underscore new file mode 100644 index 0000000000..b3b39ac5ca --- /dev/null +++ b/cms/templates/js/upload-dialog.underscore @@ -0,0 +1,16 @@ +
          +

          <%= title %>

          +

          <%= message %>

          + + <% if(uploading) { %> + <% if (uploadedBytes && totalBytes) { %> + <%= uploadedBytes/totalBytes*100 %>% + <% } else { %> + + <% } %> + <% } %> +
          + disabled="disabled"<% } %> /> + +
          +
          diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index 774f9e26af..3f82716f2f 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -11,6 +11,9 @@ + <%block name="jsextra"> @@ -60,7 +63,7 @@ CMS.Views.TextbookEdit = Backbone.View.extend({ events: { "submit": "save", "click .action-cancel": "remove", - "click .add-chapter": "createChapter" + "click .action-add-chapter": "createChapter" }, addOne: function(chapter) { var view = new CMS.Views.ChapterEdit({model: chapter}); @@ -83,6 +86,7 @@ CMS.Views.TextbookEdit = Backbone.View.extend({ CMS.Views.ChapterEdit = Backbone.View.extend({ initialize: function() { this.template = _.template($("#new-chapter-tpl").text()); + this.listenTo(this.model, "change", this.render) }, render: function() { this.$el.html(this.template({ @@ -93,14 +97,114 @@ CMS.Views.ChapterEdit = Backbone.View.extend({ return this; }, events: { - "click .action-close": "removeChapter" + "click .action-close": "removeChapter", + "click .action-uploadasset": "openUploadDialog", + "submit": "uploadAsset" }, removeChapter: function(e) { if(e && e.preventDefault) { e.preventDefault(); } this.model.collection.remove(this.model); return this.remove(); + }, + openUploadDialog: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + var msg = new CMS.Models.FileUpload({ + title: _.str.sprintf(gettext("Upload a new asset to %s"), + section.escape('name')), + message: "[asset upload requirements]" + }) + var view = new CMS.Views.UploadDialog({model: msg, chapter: this.model}) + $(".wrapper-view").append(view.render().el) } }) +CMS.Models.FileUpload = Backbone.Model.extend({ + defaults: { + "title": "", + "message": "", + "selectFile": null, + "uploading": false, + "uploadedBytes": 0, + "totalBytes": 0, + } +}); +CMS.Views.UploadDialog = Backbone.View.extend({ + initialize: function() { + this.template = _.template($("#upload-dialog-tpl").text()); + this.listenTo(this.model, "change", this.render); + }, + render: function() { + // some browsers (like Chrome) allow you to assign to the .files attribute + // of an DOM element -- for those browsers, we can + // create a new DOM element and assign the old content to it. Other browsers + // (like Firefox) make this attribute read-only, and we have to save the + // old DOM element in order to save it's content. For compatibility purposes, + // we'll just save the old element every time. + var oldInput = this.$("input[type=file]").get(0), selectedFile; + if (oldInput && oldInput.files.length) { + selectedFile = oldInput.files[0]; + } + this.$el.html(this.template({ + url: UPLOAD_ASSET_CALLBACK_URL, + title: this.model.escape('title'), + message: this.model.escape('message'), + selectedFile: selectedFile, + uploading: this.model.get('uploading'), + uploadedBytes: this.model.get('uploadedBytes'), + totalBytes: this.model.get('totalBytes'), + })) + if (oldInput) { + this.$('input[type=file]').replaceWith(oldInput); + } + + return this; + }, + events: { + "change input[type=file]": "selectFile", + "click .action-cancel": "removeSelf", + "click .action-upload": "upload" + }, + selectFile: function(e) { + this.model.set('fileList', e.target.files) + }, + removeSelf: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.remove(); + }, + upload: function(e) { + this.model.set('uploading', true); + this.$("form").ajaxSubmit({ + success: _.bind(this.success, this), + error: _.bind(this.error, this), + uploadProgress: _.bind(this.progress, this), + }); + }, + progress: function(event, position, total, percentComplete) { + this.model.set({ + "uploadedBytes": position, + "totalBytes": total, + }) + }, + success: function(response, statusText, xhr, form) { + this.model.set('uploading', false); + var chapter = this.options.chapter; + if(chapter) { + var options = {} + if(!chapter.get("name")) { + options.name = response.displayname; + } + options.asset_path = response.url; + chapter.set(options); + } + this.remove(); + }, + error: function() { + this.model.set("uploading", false); + } +}) +var section = new CMS.Models.Section({ + id: "${course.id}", + name: "${course.display_name_with_default | h}" +}); $(function() { $(".new-button").click(function() { var textbook = new CMS.Models.Textbook(); From d4b1d4c60c60e88cadcc7ea79e409d8dc2cd549a Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 24 May 2013 16:00:52 -0400 Subject: [PATCH 014/137] return right mimetype from upload method. --- cms/djangoapps/contentstore/views/assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py index 931693de29..478e5a6107 100644 --- a/cms/djangoapps/contentstore/views/assets.py +++ b/cms/djangoapps/contentstore/views/assets.py @@ -157,7 +157,7 @@ def upload_asset(request, org, course, coursename): 'msg': 'Upload completed' } - response = HttpResponse(json.dumps(response_payload)) + response = HttpResponse(json.dumps(response_payload), mimetype="application/json") response['asset_url'] = StaticContent.get_url_path_from_location(content.location) return response From e237cdca842d00127445ff0d678942de7f2f6ce4 Mon Sep 17 00:00:00 2001 From: Frances Botsford Date: Tue, 28 May 2013 14:32:18 -0400 Subject: [PATCH 015/137] round 2 on html and sass for textbook upload --- cms/static/sass/elements/_controls.scss | 20 ----------------- cms/static/sass/views/_textbooks.scss | 29 +++++++++++++++++++------ cms/templates/js/chapter.underscore | 2 +- cms/templates/js/textbook.underscore | 2 +- common/static/sass/_mixins.scss | 9 -------- 5 files changed, 24 insertions(+), 38 deletions(-) diff --git a/cms/static/sass/elements/_controls.scss b/cms/static/sass/elements/_controls.scss index 2bddbbc0a0..8de152892e 100644 --- a/cms/static/sass/elements/_controls.scss +++ b/cms/static/sass/elements/_controls.scss @@ -133,27 +133,7 @@ } } -// blue small/inline button -.btn-flat-blue { - @extend .btn-flat; - color: $white; - background-color: $blue; - &:hover, &:active { - background: $blue-l4; - color: $blue-s2; - } - - &.current, &.active { - border-color: $blue-l3; - background: $blue-l3; - color: $blue-d1; - - &:hover, &:active { - - } - } -} // ==================== diff --git a/cms/static/sass/views/_textbooks.scss b/cms/static/sass/views/_textbooks.scss index 26f4b1c4e1..c9d604eb14 100644 --- a/cms/static/sass/views/_textbooks.scss +++ b/cms/static/sass/views/_textbooks.scss @@ -16,12 +16,13 @@ body.course.textbooks { margin-top: $baseline; .action-add-chapter { - @extend .btn-flat-blue; @extend .t-action2; + @include grey-button; @include transition(all .15s); display: block; width: 100%; margin-bottom: ($baseline*1.5); + border: 1px solid #B3C4DA; padding: ($baseline/5) $baseline; font-weight: 600; } @@ -31,7 +32,7 @@ body.course.textbooks { @extend .t-action2; @include transition(all .15s); display: inline-block; - padding: ($baseline/5) $baseline ($baseline/4) $baseline; + padding: ($baseline/5) $baseline; font-weight: 600; text-transform: uppercase; } @@ -41,12 +42,25 @@ body.course.textbooks { @extend .t-action2; @include transition(all .15s); display: inline-block; - padding: ($baseline/5) $baseline ($baseline/4) $baseline; + padding: ($baseline/5) $baseline; font-weight: 600; text-transform: uppercase; } + } + + .copy { + @include font-size(12); + margin: ($baseline) 0 ($baseline/2) 0; + color: $gray; + + + strong { + font-weight: 600; + } + + } .list-input { @@ -155,7 +169,7 @@ body.course.textbooks { .field { display: block; - width: 46%; + width: 46.5%; border-bottom: none; margin: 0 $baseline 0 0; padding: ($baseline/4) 0 0 0; @@ -168,14 +182,15 @@ body.course.textbooks { .action-uploadasset { @extend .t-action4; - @extend .btn-flat-blue; + @include blue-button; @include transition(all .15s); + @include font-size(12); font-weight: 600; text-align: center; position: absolute; - top: 0; + top: 2px; right: 0; - padding: ($baseline/5) ($baseline/2) ($baseline/10) ($baseline/2); + padding: 3px ($baseline/2) 1px ($baseline/2); } } diff --git a/cms/templates/js/chapter.underscore b/cms/templates/js/chapter.underscore index 9cdf4fd76f..af3398154a 100644 --- a/cms/templates/js/chapter.underscore +++ b/cms/templates/js/chapter.underscore @@ -7,7 +7,7 @@
          " value="<%= asset_path %>" type="text"> - <%= gettext("provide the path for a file or asset already added to this course") %> + <%= gettext("provide the path to a file added to this course or upload a new one") %>
          delete chapter diff --git a/cms/templates/js/textbook.underscore b/cms/templates/js/textbook.underscore index 25dda42b26..2b955fd5e5 100644 --- a/cms/templates/js/textbook.underscore +++ b/cms/templates/js/textbook.underscore @@ -14,7 +14,7 @@
            -

            <%= gettext("Note: It's best practice to break your course's textbook into multiple chapters to reduce loading times for students. Breaking up textbooks into chapters can also help with students more easily finding a concept or topic-based information.") %>

            +

            <%= gettext("Note: It's best practice to break your course's textbook into multiple chapters to reduce loading times for students. Breaking up textbooks into chapters can also help with students more easily finding a concept or topic-based information.") %>

            diff --git a/common/static/sass/_mixins.scss b/common/static/sass/_mixins.scss index f947d05f1e..96f5170d9b 100644 --- a/common/static/sass/_mixins.scss +++ b/common/static/sass/_mixins.scss @@ -205,12 +205,3 @@ padding:($baseline/5) $baseline/2; line-height: 1.3; text-align: center; - - &:hover, &:active { - - } - - &.current, &.active { - - } -} From 0192b509ceb3cdafdfc9387c492dda72c836944e Mon Sep 17 00:00:00 2001 From: Frances Botsford Date: Tue, 28 May 2013 14:39:07 -0400 Subject: [PATCH 016/137] replaced main-wrapper with wrapper-content wrapper for main content to align better --- cms/templates/textbooks.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index 3f82716f2f..1c5410ae76 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -234,7 +234,7 @@ $(function() {
            -
            +
            - delete chapter + <%= gettext("delete chapter") %> diff --git a/cms/templates/js/textbook.underscore b/cms/templates/js/textbook.underscore index d28c034359..02b37a0121 100644 --- a/cms/templates/js/textbook.underscore +++ b/cms/templates/js/textbook.underscore @@ -1,7 +1,7 @@
            - Required information to edit or add a textbook and chapters + <%= gettext("Required information to edit or add a textbook and chapters") %>
            1. diff --git a/cms/templates/js/upload-dialog.underscore b/cms/templates/js/upload-dialog.underscore index b3b39ac5ca..0270525541 100644 --- a/cms/templates/js/upload-dialog.underscore +++ b/cms/templates/js/upload-dialog.underscore @@ -1,6 +1,10 @@ + + +
              +

              You haven't added any textbooks to this course yet.

              +

              Add your first textbook

              +
              +
            + + + + From f198b7e297b0f4f8ff065512bd8b9b678814ce43 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Fri, 31 May 2013 13:37:27 -0400 Subject: [PATCH 023/137] Clean up Backbone view logic --- cms/templates/js/upload-dialog.underscore | 7 +++++- cms/templates/textbooks.html | 28 +++++++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/cms/templates/js/upload-dialog.underscore b/cms/templates/js/upload-dialog.underscore index 0270525541..5994cabcb8 100644 --- a/cms/templates/js/upload-dialog.underscore +++ b/cms/templates/js/upload-dialog.underscore @@ -1,4 +1,9 @@ -