From f3032ba7cf835f479a5254a1f08784aa860cfeee Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 18 Dec 2012 11:35:25 -0500 Subject: [PATCH] Make course textbooks and wiki slugs work --- common/lib/xmodule/xmodule/course_module.py | 132 +++++++++++--------- 1 file changed, 73 insertions(+), 59 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index ad68b9a1b3..947d75eb97 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -4,7 +4,7 @@ from path import path # NOTE (THK): Only used for detecting presence of syllabus from xmodule.graders import grader_from_conf from xmodule.modulestore import Location from xmodule.seq_module import SequenceDescriptor, SequenceModule -from xmodule.timeparse import parse_time, stringify_time +from xmodule.timeparse import parse_time from xmodule.util.decorators import lazyproperty import json import logging @@ -20,10 +20,79 @@ log = logging.getLogger(__name__) edx_xml_parser = etree.XMLParser(dtd_validation=False, load_dtd=False, remove_comments=True, remove_blank_text=True) +class Textbook(object): + def __init__(self, title, book_url): + self.title = title + self.book_url = book_url + self.start_page = int(self.table_of_contents[0].attrib['page']) + + # The last page should be the last element in the table of contents, + # but it may be nested. So recurse all the way down the last element + last_el = self.table_of_contents[-1] + while last_el.getchildren(): + last_el = last_el[-1] + + self.end_page = int(last_el.attrib['page']) + + @lazyproperty + def table_of_contents(self): + """ + Accesses the textbook's table of contents (default name "toc.xml") at the URL self.book_url + + Returns XML tree representation of the table of contents + """ + toc_url = self.book_url + 'toc.xml' + + # Get the table of contents from S3 + log.info("Retrieving textbook table of contents from %s" % toc_url) + try: + r = requests.get(toc_url) + except Exception as err: + msg = 'Error %s: Unable to retrieve textbook table of contents at %s' % (err, toc_url) + log.error(msg) + raise Exception(msg) + + # TOC is XML. Parse it + try: + table_of_contents = etree.fromstring(r.text) + except Exception as err: + msg = 'Error %s: Unable to parse XML for textbook table of contents at %s' % (err, toc_url) + log.error(msg) + raise Exception(msg) + + return table_of_contents + + +class TextbookList(ModelType): + def from_json(self, values): + textbooks = [] + for title, book_url in values: + try: + textbooks.append(Textbook(title, book_url)) + except: + # If we can't get to S3 (e.g. on a train with no internet), don't break + # the rest of the courseware. + log.exception("Couldn't load textbook ({0}, {1})".format(title, book_url)) + continue + + return textbooks + + def to_json(self, values): + json_data = [] + for val in values: + if isinstance(val, Textbook): + json_data.append((textbook.title, textbook.book_url)) + elif isinstance(val, tuple): + json_data.append(val) + else: + continue + return json_data + + class CourseDescriptor(SequenceDescriptor): module_class = SequenceModule - textbooks = List(help="List of pairs of (title, url) for textbooks used in this course", default=[], scope=Scope.content) + textbooks = TextbookList(help="List of pairs of (title, url) for textbooks used in this course", default=[], scope=Scope.content) wiki_slug = String(help="Slug that points to the wiki for this course", scope=Scope.content) enrollment_start = Date(help="Date that enrollment for this class is opened", scope=Scope.settings) enrollment_end = Date(help="Date that enrollment for this class is closed", scope=Scope.settings) @@ -70,65 +139,10 @@ class CourseDescriptor(SequenceDescriptor): template_dir_name = 'course' - class Textbook: - def __init__(self, title, book_url): - self.title = title - self.book_url = book_url - self.table_of_contents = self._get_toc_from_s3() - self.start_page = int(self.table_of_contents[0].attrib['page']) - - # The last page should be the last element in the table of contents, - # but it may be nested. So recurse all the way down the last element - last_el = self.table_of_contents[-1] - while last_el.getchildren(): - last_el = last_el[-1] - - self.end_page = int(last_el.attrib['page']) - - @property - def table_of_contents(self): - return self.table_of_contents - - def _get_toc_from_s3(self): - """ - Accesses the textbook's table of contents (default name "toc.xml") at the URL self.book_url - - Returns XML tree representation of the table of contents - """ - toc_url = self.book_url + 'toc.xml' - - # Get the table of contents from S3 - log.info("Retrieving textbook table of contents from %s" % toc_url) - try: - r = requests.get(toc_url) - except Exception as err: - msg = 'Error %s: Unable to retrieve textbook table of contents at %s' % (err, toc_url) - log.error(msg) - raise Exception(msg) - - # TOC is XML. Parse it - try: - table_of_contents = etree.fromstring(r.text) - except Exception as err: - msg = 'Error %s: Unable to parse XML for textbook table of contents at %s' % (err, toc_url) - log.error(msg) - raise Exception(msg) - - return table_of_contents def __init__(self, *args, **kwargs): super(CourseDescriptor, self).__init__(*args, **kwargs) - self.textbooks = [] - for title, book_url in self.textbooks: - try: - self.textbooks.append(self.Textbook(title, book_url)) - except: - # If we can't get to S3 (e.g. on a train with no internet), don't break - # the rest of the courseware. - log.exception("Couldn't load textbook ({0}, {1})".format(title, book_url)) - continue - if self.wiki_slug is None: self.wiki_slug = self.location.course @@ -272,8 +286,8 @@ class CourseDescriptor(SequenceDescriptor): definition, children = super(CourseDescriptor, cls).definition_from_xml(xml_object, system) - definition.setdefault('data', {})['textbooks'] = textbooks - definition['data']['wiki_slug'] = wiki_slug + definition['textbooks'] = textbooks + definition['wiki_slug'] = wiki_slug return definition, children