From b5ab766092c340da375d6accee111c4478508a6c Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Wed, 6 Mar 2013 17:57:33 -0500 Subject: [PATCH 1/8] initial framework for htmlbook --- common/lib/xmodule/xmodule/course_module.py | 7 ++++ lms/djangoapps/courseware/tabs.py | 11 ++++++ lms/djangoapps/staticbook/views.py | 40 +++++++++++++++++++-- lms/urls.py | 5 +++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 72196f92a2..3923d3f056 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -358,6 +358,13 @@ class CourseDescriptor(SequenceDescriptor): """ return self.metadata.get('pdf_textbooks') + @property + def html_textbooks(self): + """ + Return the html_textbooks config, as a python object, or None if not specified. + """ + return self.metadata.get('html_textbooks') + @tabs.setter def tabs(self, value): self.metadata['tabs'] = value diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index b31aeb6b5a..4a9de0792f 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -130,6 +130,17 @@ def _pdf_textbooks(tab, user, course, active_page): for index, textbook in enumerate(course.pdf_textbooks)] return [] +def _html_textbooks(tab, user, course, active_page): + """ + Generates one tab per textbook. Only displays if user is authenticated. + """ + if user.is_authenticated(): + # since there can be more than one textbook, active_page is e.g. "book/0". + return [CourseTab(textbook['tab_title'], reverse('html_book', args=[course.id, index]), + active_page == "htmltextbook/{0}".format(index)) + for index, textbook in enumerate(course.html_textbooks)] + return [] + def _staff_grading(tab, user, course, active_page): if has_access(user, course, 'staff'): link = reverse('staff_grading', args=[course.id]) diff --git a/lms/djangoapps/staticbook/views.py b/lms/djangoapps/staticbook/views.py index a391b1cb32..8e973ac97d 100644 --- a/lms/djangoapps/staticbook/views.py +++ b/lms/djangoapps/staticbook/views.py @@ -57,12 +57,46 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None): # then remap all the chapter URLs as well, if they are provided. if 'chapters' in textbook: for entry in textbook['chapters']: - entry['url'] = remap_static_url(entry['url'], course) + entry['url'] = remap_static_url(entry['url'], course) return render_to_response('static_pdfbook.html', - {'book_index': book_index, - 'course': course, + {'book_index': book_index, + 'course': course, + 'textbook': textbook, + 'chapter': chapter, + 'page': page, + 'staff_access': staff_access}) + +@login_required +def html_index(request, course_id, book_index, chapter=None, page=None): + course = get_course_with_access(request.user, course_id, 'load') + staff_access = has_access(request.user, course, 'staff') + + book_index = int(book_index) + textbook = course.html_textbooks[book_index] + + def remap_static_url(original_url, course): + input_url = "'" + original_url + "'" + output_url = replace_static_urls( + input_url, + course.metadata['data_dir'], + course_namespace=course.location + ) + # strip off the quotes again... + return output_url[1:-1] + + if 'url' in textbook: + textbook['url'] = remap_static_url(textbook['url'], course) + # then remap all the chapter URLs as well, if they are provided. + if 'chapters' in textbook: + for entry in textbook['chapters']: + entry['url'] = remap_static_url(entry['url'], course) + + + return render_to_response('static_htmlbook.html', + {'book_index': book_index, + 'course': course, 'textbook': textbook, 'chapter': chapter, 'page': page, diff --git a/lms/urls.py b/lms/urls.py index 5e5ac9a7f2..b85bc3d458 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -285,6 +285,11 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/pdfbook/(?P[^/]*)/chapter/(?P[^/]*)/(?P[^/]*)$', 'staticbook.views.pdf_index'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P[^/]*)/$', + 'staticbook.views.html_index', name="html_book"), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P[^/]*)/(?P[^/]*)$', + 'staticbook.views.html_index'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/?$', 'courseware.views.index', name="courseware"), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/(?P[^/]*)/$', From aee7d856942b403b768f9a0a9781502777c99401 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Thu, 7 Mar 2013 02:01:42 -0500 Subject: [PATCH 2/8] fix bug in tabs --- lms/djangoapps/courseware/tabs.py | 1 + lms/templates/static_htmlbook.html | 71 ++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 lms/templates/static_htmlbook.html diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 4a9de0792f..a956c03eeb 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -220,6 +220,7 @@ VALID_TAB_TYPES = { 'external_link': TabImpl(key_checker(['name', 'link']), _external_link), 'textbooks': TabImpl(null_validator, _textbooks), 'pdf_textbooks': TabImpl(null_validator, _pdf_textbooks), + 'html_textbooks': TabImpl(null_validator, _html_textbooks), 'progress': TabImpl(need_name, _progress), 'static_tab': TabImpl(key_checker(['name', 'url_slug']), _static_tab), 'peer_grading': TabImpl(null_validator, _peer_grading), diff --git a/lms/templates/static_htmlbook.html b/lms/templates/static_htmlbook.html new file mode 100644 index 0000000000..e3caa9c0ce --- /dev/null +++ b/lms/templates/static_htmlbook.html @@ -0,0 +1,71 @@ +<%inherit file="main.html" /> +<%namespace name='static' file='static_content.html'/> +<%block name="title"> + + + ${course.number} Textbook + + +<%block name="headextra"> +<%static:css group='course'/> +<%static:js group='courseware'/> + + +<%block name="js_extra"> + + + +<%include file="/courseware/course_navigation.html" args="active_page='htmltextbook/{0}'.format(book_index)" /> + +
+
+ + %if 'chapters' in textbook: +
+
    + <%def name="print_entry(entry, index_value)"> +
  • + + ${entry.get('title')} + +
  • + + + % for (index, entry) in enumerate(textbook['chapters']): + ${print_entry(entry, index+1)} + % endfor +
+
+ %endif + +
+ + ") + parentElement = document.getElementById('bookpage'); + while (parentElement.hasChildNodes()) + parentElement.removeChild(parentElement.lastChild); + + $('"') + // .css({'height':'40px','width':'200px'}) + .appendTo('#bookpage'); + } + }; + + loadUrl = function htmlViewLoadUrl(url, anchorId) { + var newurl = url; + if (anchorId != null) { + newurl = url + "#" + anchorId; + } + // clear out previous load, if any: + parentElement = document.getElementById('bookpage'); + while (parentElement.hasChildNodes()) + parentElement.removeChild(parentElement.lastChild); + + $('#bookpage').load(newurl); + + }; + + loadChapterUrl = function htmlViewLoadChapterUrl(chapterNum, anchorId) { + if (chapterNum < 1 || chapterNum > chapterUrls.length) { + return; + } + var chapterUrl = chapterUrls[chapterNum-1]; + loadUrl(chapterUrl, anchorId); + }; + + // define navigation links for chapters: + if (chapterUrls != null) { + var loadChapterUrlHelper = function(i) { + return function(event) { + // when opening a new chapter, always open to the top: + loadChapterUrl(i, null); + }; + }; + for (var index = 1; index <= chapterUrls.length; index += 1) { + $("#htmlchapter-" + index).click(loadChapterUrlHelper(index)); + } + } + + // finally, load the appropriate url/page + if (urlToLoad != null) { + loadUrl(urlToLoad, anchorToLoad); + } else { + loadChapterUrl(chapterToLoad, anchorToLoad); + } + + } +})(jQuery); $(document).ready(function() { var options = {}; @@ -29,10 +105,8 @@ %if chapter is not None: options.chapterNum = ${chapter}; %endif - %if page is not None: - options.pageNum = ${page}; - %endif + $('#outerContainer').myHTMLViewer(options); }); @@ -43,29 +117,29 @@
%if 'chapters' in textbook: -
-
    - <%def name="print_entry(entry, index_value)"> -
  • - - ${entry.get('title')} - -
  • - +
    +
      + <%def name="print_entry(entry, index_value)"> +
    • + + ${entry.get('title')} + +
    • + - % for (index, entry) in enumerate(textbook['chapters']): - ${print_entry(entry, index+1)} - % endfor -
    -
    + %for (index, entry) in enumerate(textbook['chapters']): + ${print_entry(entry, index+1)} + % endfor +
+
%endif
- - ") - parentElement = document.getElementById('bookpage'); - while (parentElement.hasChildNodes()) - parentElement.removeChild(parentElement.lastChild); - - $('"') - // .css({'height':'40px','width':'200px'}) - .appendTo('#bookpage'); - } - }; + if (options.chapters) { + anchorToLoad = options.anchor_id; + } loadUrl = function htmlViewLoadUrl(url, anchorId) { - var newurl = url; - if (anchorId != null) { - newurl = url + "#" + anchorId; - } // clear out previous load, if any: parentElement = document.getElementById('bookpage'); while (parentElement.hasChildNodes()) parentElement.removeChild(parentElement.lastChild); + // load new URL in: + $('#bookpage').load(url); - $('#bookpage').load(newurl); + // if there is an anchor set, then go to that location: + if (anchorId != null) { + // TODO: add implementation.... + } }; @@ -105,6 +92,9 @@ %if chapter is not None: options.chapterNum = ${chapter}; %endif + %if anchor_id is not None: + options.anchor_id = ${anchor_id}; + %endif $('#outerContainer').myHTMLViewer(options); }); diff --git a/lms/urls.py b/lms/urls.py index b85bc3d458..3377fa54c0 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -287,7 +287,11 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P[^/]*)/$', 'staticbook.views.html_index', name="html_book"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P[^/]*)/(?P[^/]*)$', + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P[^/]*)/chapter/(?P[^/]*)/$', + 'staticbook.views.html_index'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P[^/]*)/chapter/(?P[^/]*)/(?P[^/]*)/$', + 'staticbook.views.html_index'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/htmlbook/(?P[^/]*)/(?P[^/]*)/$', 'staticbook.views.html_index'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/?$', From fcb618f04eedd65602914582546c57aed77ad30b Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Mon, 11 Mar 2013 10:38:32 -0400 Subject: [PATCH 5/8] fix basic alignment issue with html textbook display --- lms/static/sass/course/_textbook.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lms/static/sass/course/_textbook.scss b/lms/static/sass/course/_textbook.scss index af9c2493fd..aba076af5b 100644 --- a/lms/static/sass/course/_textbook.scss +++ b/lms/static/sass/course/_textbook.scss @@ -158,6 +158,10 @@ div.book-wrapper { img { max-width: 100%; } + + div { + text-align: left; + } } } From 2199ae673b1895ae5a004b6dcc88bca67beecf61 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Mon, 11 Mar 2013 11:10:08 -0400 Subject: [PATCH 6/8] add checks that book_index is in range --- lms/djangoapps/staticbook/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/staticbook/views.py b/lms/djangoapps/staticbook/views.py index 72c72e3154..ec34683997 100644 --- a/lms/djangoapps/staticbook/views.py +++ b/lms/djangoapps/staticbook/views.py @@ -1,7 +1,7 @@ from lxml import etree -# from django.conf import settings from django.contrib.auth.decorators import login_required +from django.http import Http404 from mitxmako.shortcuts import render_to_response from courseware.access import has_access @@ -15,6 +15,8 @@ def index(request, course_id, book_index, page=None): staff_access = has_access(request.user, course, 'staff') book_index = int(book_index) + if book_index < 0 or book_index >= len(course.textbooks): + raise Http404("Invalid book index value: {0}".format(book_index)) textbook = course.textbooks[book_index] table_of_contents = textbook.table_of_contents @@ -40,6 +42,8 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None): staff_access = has_access(request.user, course, 'staff') book_index = int(book_index) + if book_index < 0 or book_index >= len(course.pdf_textbooks): + raise Http404("Invalid book index value: {0}".format(book_index)) textbook = course.pdf_textbooks[book_index] def remap_static_url(original_url, course): @@ -74,6 +78,8 @@ def html_index(request, course_id, book_index, chapter=None, anchor_id=None): staff_access = has_access(request.user, course, 'staff') book_index = int(book_index) + if book_index < 0 or book_index >= len(course.html_textbooks): + raise Http404("Invalid book index value: {0}".format(book_index)) textbook = course.html_textbooks[book_index] def remap_static_url(original_url, course): From cd95872b52fa1f6b7b5055f5312573049e37a4f3 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Mon, 11 Mar 2013 11:56:48 -0400 Subject: [PATCH 7/8] htmlbook-specific styling to make Heroes text presentable. --- lms/static/sass/course/_textbook.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lms/static/sass/course/_textbook.scss b/lms/static/sass/course/_textbook.scss index aba076af5b..b1f3a863b8 100644 --- a/lms/static/sass/course/_textbook.scss +++ b/lms/static/sass/course/_textbook.scss @@ -161,6 +161,15 @@ div.book-wrapper { div { text-align: left; + line-height: 1.6em; + margin-left: 5px; + margin-right: 5px; + margin-top: 5px; + margin-bottom: 5px; + + .Paragraph, h2 { + margin-top: 10px; + } } } } From 53f85f2a07438fe84cfc6cda77537419a619a7fd Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Mon, 11 Mar 2013 13:52:43 -0400 Subject: [PATCH 8/8] return empty list if no html or pdf textbook appears in policy.json --- common/lib/xmodule/xmodule/course_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 3923d3f056..1c9928a502 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -356,14 +356,14 @@ class CourseDescriptor(SequenceDescriptor): """ Return the pdf_textbooks config, as a python object, or None if not specified. """ - return self.metadata.get('pdf_textbooks') + return self.metadata.get('pdf_textbooks', []) @property def html_textbooks(self): """ Return the html_textbooks config, as a python object, or None if not specified. """ - return self.metadata.get('html_textbooks') + return self.metadata.get('html_textbooks', []) @tabs.setter def tabs(self, value):