def _toc(depth, toc_items): items = [] for toc_item in toc_items: if isinstance(toc_item[1], list): section_title, chapters = toc_item items += [{ 'type': 'section', 'level': depth, 'title': section_title, 'url_title': booktype_slugify(section_title), }] items += _toc(depth + 1, chapters) else: chapter_title, chapter_href = toc_item chapter_item = book.get_item_with_href(chapter_href) content = self._get_chapter_content(chapter_item) content = self._fix_horrible_mpdf(content) href_filename, file_extension = os.path.splitext( chapter_href) items.append({ 'type': 'chapter', 'level': depth, 'title': chapter_title, 'url_title': booktype_slugify(chapter_title), 'href': chapter_href, 'href_filename': href_filename, 'content': content }) return items
def _toc(depth, toc_items): items = [] for toc_item in toc_items: if isinstance(toc_item[1], list): section_title, chapters = toc_item items += [{ 'type': 'section', 'level': depth, 'title': section_title, 'url_title': booktype_slugify(section_title), }] items += _toc(depth + 1, chapters) else: chapter_title, chapter_href = toc_item chapter_item = book.get_item_with_href(chapter_href) content = self._get_chapter_content(chapter_item) content = self._fix_horrible_mpdf(content) href_filename, file_extension = os.path.splitext(chapter_href) items.append({ 'type': 'chapter', 'level': depth, 'title': chapter_title, 'url_title': booktype_slugify(chapter_title), 'href': chapter_href, 'href_filename': href_filename, 'content': content}) return items
def _toc(depth, toc_items, parent=None, toc_setting=None): items = [] sec_count = 1 for toc_item in toc_items: # SECTIONS if isinstance(toc_item[1], list): section_title, chapters = toc_item url_title = booktype_slugify(section_title) # let's build a section key and try to get settings for current section section_key = SectionsSettingsPlugin.build_section_key(url_title, sec_count) section_settings = json.loads(settings_dict.get(section_key, '{}')) toc_setting = section_settings.get('toc', {}).get(self.name, '') # jump to next item (continue) if the whole section should be hidden show_in_outputs = section_settings.get('show_in_outputs', {}) show_section_in_current_converter = show_in_outputs.get(self.name, True) if not show_section_in_current_converter: continue toc_item = TocItem({ 'type': 'section', 'level': depth, 'title': section_title, 'url_title': url_title, 'show_in_toc': 'hide_section' not in toc_setting }) items.append(toc_item) items += _toc(depth + 1, chapters, section_title, toc_setting) sec_count += 1 # CHAPTERS else: chapter_title, chapter_href = toc_item chapter_item = book.get_item_with_href(chapter_href) content = self._get_chapter_content(chapter_item) content = self._fix_horrible_mpdf(content) href_filename, file_extension = os.path.splitext(chapter_href) if not parent: toc_setting = '' toc_item = TocItem({ 'type': 'chapter', 'level': depth, 'title': chapter_title, 'url_title': booktype_slugify(chapter_title), 'href': chapter_href, 'href_filename': href_filename, 'content': content, 'show_in_toc': 'hide_chapters' not in toc_setting }) items.append(toc_item) return items
def _toc(depth, toc_items, parent=None, toc_setting=None): items = [] sec_count = 1 for toc_item in toc_items: if isinstance(toc_item[1], list): section_title, chapters = toc_item url_title = booktype_slugify(section_title) key = 'section_%s_%s' % (url_title, sec_count) section_settings = json.loads(settings.get(key, '{}')) toc_setting = section_settings.get('toc', {}).get(self.name, '') # continue if the whole section should be hidden show_in_outputs = section_settings.get('show_in_outputs', {}) if show_in_outputs.get(self.name, 'true') == 'false': continue items += [{ 'type': 'section', 'level': depth, 'title': section_title, 'url_title': url_title, 'show_in_toc': 'hide_section' not in toc_setting }] items += _toc(depth + 1, chapters, section_title, toc_setting) sec_count += 1 else: chapter_title, chapter_href = toc_item chapter_item = book.get_item_with_href(chapter_href) content = self._get_chapter_content(chapter_item) content = self._fix_horrible_mpdf(content) href_filename, file_extension = os.path.splitext(chapter_href) if not parent: toc_setting = '' items.append({ 'type': 'chapter', 'level': depth, 'title': chapter_title, 'url_title': booktype_slugify(chapter_title), 'href': chapter_href, 'href_filename': href_filename, 'content': content, 'show_in_toc': 'hide_chapters' not in toc_setting }) return items
def upload_attachment(request, bookid, version=None): try: book = models.Book.objects.get(url_title__iexact=bookid) except models.Book.DoesNotExist: return views.ErrorPage( request, "errors/book_does_not_exist.html", {"book_name": bookid}) user = request.user book_security = security.get_security_for_book(user, book) can_upload_attachment = book_security.has_perm('edit.upload_attachment') if (not user.is_superuser and not can_upload_attachment and book.owner != user): raise PermissionDenied book_version = book.get_version(version) stat = models.BookStatus.objects.filter(book=book)[0] with transaction.atomic(): file_data = request.FILES['files[]'] att = models.Attachment( version=book_version, # must remove this reference created=datetime.datetime.now(), book=book, status=stat ) att.save() attName, attExt = os.path.splitext(file_data.name) att.attachment.save( '{}{}'.format(booktype_slugify(attName), attExt), file_data, save=False ) att.save() response_data = { "files": [{ "url": "http://127.0.0.1/", "thumbnail_url": "http://127.0.0.1/", "name": "boot.png", "type": "image/png", "size": 172728, "delete_url": "", "delete_type": "DELETE" }] } # add cliendID and sputnikID to request object # this will allow us to use sputnik and addMessageToChannel request.clientID = request.POST['clientID'] request.sputnikID = "%s:%s" % (request.session.session_key, request.clientID) send_notification(request, book.id, book_version.get_version(), "notification_new_attachment_uploaded", att.get_name()) if "application/json" in request.META['HTTP_ACCEPT']: return HttpResponse(json.dumps(response_data), content_type="application/json") else: return HttpResponse(json.dumps(response_data), content_type="text/html")
def validate(self, attrs): attrs['book'] = self.context['view']._book attrs['content'] = u'<h1>{}</h1><p><br/></p>'.format(attrs['title']) attrs['content_json'] = u'''{ "entityMap": {}, "blocks": [ { "key": "bm8jb", "text": "", "type": "datablock", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], "data": {} }, { "key": "f29sf", "text": "Chapter Title", "type": "heading1", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], "data": { "attributes": { "style": {} } } }, { "key": "a4d8p", "text": "", "type": "unstyled", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], "data": {} } ] }''' attrs['url_title'] = booktype_slugify(attrs['title']) attrs['version'] = attrs['book'].version attrs['status'] = BookStatus.objects.filter(book=attrs['book']).order_by("-weight")[0] # validate title/url_title if not len(attrs['url_title']): error_msg = {'title': 'Title is empty or contains wrong characters.'} logger.warn('ChapterListCreateSerializer validate: {}'.format(error_msg)) raise serializers.ValidationError(error_msg) # validate title/url_title chapter_exists = Chapter.objects.filter( book=self.context['view']._book, version=attrs['book'].version, url_title=attrs['url_title'] ).exists() if chapter_exists: error_msg = {'title': 'Chapter with this title already exists.'} logger.warn('ChapterListCreateSerializer validate: {}'.format(error_msg)) raise serializers.ValidationError(error_msg) return attrs
def books(request): books = [] for title in request.param: books.append( BookFactory(title=title, url_title=booktype_slugify(title)) ) return books
def _set_cover(self, book, cover_image): """ Assigns the specified cover. """ validity = self.delegate.is_valid_cover(cover_image) if validity is None: # not checked by the assigned delegate is_valid, reason = is_valid_cover(cover_image) elif isinstance(validity, bool): is_valid, reason = validity, None else: is_valid, reason = validity if not is_valid: if reason: self.notifier.warning( _("Not using {} as a cover image -- {}").format( cover_image.file_name, reason)) else: self.notifier.warning( _("Not using {} as a cover image").format( cover_image.file_name)) return cover_file = ContentFile(cover_image.get_content()) file_name = os.path.basename(cover_image.file_name) attName, attExt = os.path.splitext(file_name) file_name = '{}{}'.format(booktype_slugify(attName), attExt) created = datetime.datetime.now() title = '' h = hashlib.sha1() h.update(cover_image.file_name) h.update(title) h.update(str(created)) cover = models.BookCover( book=book, user=book.owner, cid=h.hexdigest(), title=title, filename=file_name[:250], width=0, height=0, approved=False, is_book=False, is_ebook=True, is_pdf=False, created=created ) cover.save() cover.attachment.save(file_name, cover_file, save=False) cover.save() self.notifier.info(_("Using {} as cover image").format(file_name))
def clean_name(self): new_url_name = booktype_slugify(self.cleaned_data['name']) group_data_url_name = BookiGroup.objects.filter(url_name=new_url_name).exclude(pk=self.instance.pk) if len(group_data_url_name) > 0: raise ValidationError(_('Group name is already in use')) return self.cleaned_data.get('name', '')
def _get_section_key(self, title, count): """ Generates a key to get the section settings :Args: - `String` title - `Integer` count to make it unique """ return "section_%s_%s" % (booktype_slugify(title), count)
def form_valid(self, form): logger.debug('ImporterView::form_valid') book_file = form.cleaned_data.get('book_file') ext = get_file_extension(book_file.name) logger.debug('ImporterView::Importing file extension is "{}".'.format(ext.encode('utf8'))) default_book_title = self.get_default_title(book_file, ext) book_title = form.cleaned_data.get('book_title', default_book_title) logger.debug('ImporterView::book_title="{}" default_book_title="{}".'.format( book_title.encode('utf8'), default_book_title.encode('utf8'))) # in case book title in form is empty string if len(book_title) == 0: book_title = default_book_title if not check_book_availability(book_title): registered = Book.objects.filter(title__startswith=book_title).count() book_title = '%s %s' % (book_title, registered) logger.debug('ImporterView::Checking book availability: "{}".'.format(book_title.encode('utf8'))) book_url = booktype_slugify(book_title) book = create_book(self.request.user, book_title, book_url=book_url) logger.debug('ImporterView::Book created with url title="{}".'.format(book_url)) # check if book will be hidden and set to book book.hidden = form.cleaned_data.get('hidden') book.save() notifier = CollectNotifier() delegate = Delegate() response = {} try: book_importer = importer_utils.get_importer_module(ext) except KeyError: logger.error('ImporterView::No importer for this extension') response_data = dict(errors=[ugettext('Extension not supported!')]) return self.render_json_response(response_data) try: book_importer( book_file, book, notifier=notifier, delegate=delegate) logger.debug('ImporterView::Book imported.') response['url'] = reverse('reader:infopage', args=[book.url_title]) except Exception as e: logger.error('ImporterView::Some kind of error while importing book.') logger.exception(e) notifier.errors.append(str(e)) response['infos'] = notifier.infos response['warnings'] = notifier.warnings response['errors'] = notifier.errors return self.render_json_response(response)
def build_section_key(title, count): """ Generates a key to get/set the section settings :Args: - `str` title - `int` count to make it unique """ return 'section_%s_%s' % (booktype_slugify(title), count)
def _make_url_title(title, i=0): url_title = booktype_slugify(title) if i > 0: url_title += "_" + str(i) if url_title not in url_titles: url_titles.append(url_title) return url_title else: return _make_url_title(title, i + 1)
def _get_section_key(self, title, count): """ Generates a key to get the section settings :Args: - `String` title - `Integer` count to make it unique """ return 'section_%s_%s' % (booktype_slugify(title), count)
def import_based_on_epub(epub_file, book_dest): """ It will import an epub file into a existent book on the system. This will also try to import sections settings and stuff Keyword arguments: epub_file -- EPUB file to be imported into book_dest book_dest -- Destiny book TODO: add docstrings of return info """ notifier = CollectNotifier() delegate = Delegate() epub_importer = EpubImporter() epub_importer.notifier = notifier epub_importer.delegate = delegate result = {} try: epub_book = epub_importer.import_file(epub_file, book_dest) except Exception as e: epub_book = None logger.error('ImporterView::Some kind of error while importing book.') logger.exception(e) notifier.errors.append(str(e)) # let's try to save sections settings if epub_book is not None: settings_dict = get_sections_settings(epub_book) book_dest_version = book_dest.get_version(None) sec_count = 1 for toc_item in book_dest_version.get_toc(): if toc_item.is_section(): url_title = booktype_slugify(toc_item.name) section_key = SectionsSettingsPlugin.build_section_key( url_title, sec_count) section_settings = settings_dict.get(section_key, None) if section_settings is not None: toc_item.settings = section_settings toc_item.save() sec_count += 1 result['infos'] = notifier.infos result['warnings'] = notifier.warnings result['errors'] = notifier.errors return result
def convert_file_name(file_name): name = os.path.basename(file_name) if name.rfind('.') != -1: _np = name[:name.rfind('.')] _ext = name[name.rfind('.'):] name = booktype_slugify(_np) + _ext name = urllib.unquote(name) name = name.replace(' ', '_') return name
def convert_file_name(file_name): name = os.path.basename(file_name) if name.rfind('.') != -1: _np = name[:name.rfind('.')] _ext = name[name.rfind('.'):] name = booktype_slugify(_np)+_ext name = urllib.unquote(name) name = name.replace(' ', '_') return name
def makeTitleUnique(requestedTitle): """If <requestedTitle> is unused, return that. Otherwise, return a title in the form `u'%s - %d' % (requestedTitle, n)` where n is the lowest non-clashing positive integer. """ n = 0 name = requestedTitle while True: titles = models.Book.objects.filter(title=name).count() urls = models.Book.objects.filter(url_title=booktype_slugify(name)).count() if not titles and not urls: return name n += 1 name = u'%s - %d' % (requestedTitle, n)
def save_settings(self, request): group = self.save(commit=False) group.url_name = misc.booktype_slugify(group.name) group.created = timezone.now() group.save() # auto-join owner as team member group.members.add(request.user) # set group image if exists in post data group_image = self.files.get('group_image', None) if group_image: self.set_group_image(group.pk, group_image) return group
def form_valid(self, form): group = form.save(commit=False) group.owner = self.request.user group.url_name = booktype_slugify(group.name) group.created = timezone.now() group.save() # auto-join owner as team member group.members.add(self.request.user) # set group image if exists in post data group_image = form.files.get('group_image', None) if group_image: form.set_group_image(group.pk, group_image) return super(GroupCreateView, self).form_valid(form)
def create(self, validated_data): n = Book.objects.count() book_title = validated_data['title'] owner = validated_data['owner'] url_title = '%s-%s' % (n, booktype_slugify(book_title)) book = create_book(owner, book_title, book_url=url_title) book.language = validated_data.get('language', None) book.save() import_book_url = validated_data.get('import_book_url') import_format = validated_data.get('import_book_format') if import_book_url: book_file = self._get_book_file(import_book_url) try: book_importer = importer_utils.get_importer_module( import_format) except Exception as err: error = "Wrong importer format {}".format(err) logger.warn('BookCreateSerializer create: {}'.format(error)) raise serializers.ValidationError(error) delegate = Delegate() notifier = CollectNotifier() try: book_importer(book_file, book, notifier=notifier, delegate=delegate) except Exception as err: error_msg = "Unexpected error while importing the file {}".format( err) logger.warn( 'BookCreateSerializer create: {}'.format(error_msg)) raise APIException(error_msg) if len(notifier.errors) > 0: err = "\n".join(notifier.errors) error_msg = "Something went wrong: {}".format(err) logger.warn( 'BookCreateSerializer create: {}'.format(error_msg)) raise APIException(error_msg) return book
def _set_sections_settings(self): """ Stores the sections settings inside the book metadata that would be used by converter scripts. Using metadata give us the advantage of being still generating a valid epub in case these settings are not removed. :Args: - self (:class:`ExportBook`): current class instance """ from booktype.utils.misc import booktype_slugify settings = {} count = 1 for item in self.book_version.get_toc(): if item.is_section() and item.settings: settings['section_%s_%s' % (booktype_slugify(item.name), count)] = item.settings count += 1 self.epub_book.add_metadata( None, 'meta', json.dumps(settings), {'property': 'bkterms:sections_settings'})
def create(self, validated_data): n = Book.objects.count() book_title = validated_data['title'] owner = validated_data['owner'] url_title = '%s-%s' % (n, booktype_slugify(book_title)) book = create_book(owner, book_title, book_url=url_title) book.language = validated_data.get('language', None) book.save() import_book_url = validated_data.get('import_book_url') import_format = validated_data.get('import_book_format') if import_book_url: book_file = self._get_book_file(import_book_url) try: book_importer = importer_utils.get_importer_module(import_format) except Exception as err: error = "Wrong importer format {}".format(err) logger.warn('BookCreateSerializer create: {}'.format(error)) raise serializers.ValidationError(error) delegate = Delegate() notifier = CollectNotifier() try: book_importer(book_file, book, notifier=notifier, delegate=delegate) except Exception as err: error_msg = "Unexpected error while importing the file {}".format(err) logger.warn('BookCreateSerializer create: {}'.format(error_msg)) raise APIException(error_msg) if len(notifier.errors) > 0: err = "\n".join(notifier.errors) error_msg = "Something went wrong: {}".format(err) logger.warn('BookCreateSerializer create: {}'.format(error_msg)) raise APIException(error_msg) return book
def _import_book(self, epub_book, book): titles = {} toc = [] def _parse_toc(elements, parent=None): for _elem in elements: # used later to get parent of an elem unique_id = uuid.uuid4().hex if isinstance(_elem, tuple): toc.append((1, _elem[0].title, unique_id, parent)) _parse_toc(_elem[1], unique_id) elif isinstance(_elem, ebooklib.epub.Link): _urlp = urlparse.urlparse(_elem.href) _name = os.path.normpath(urllib.unquote(_urlp.path)) # check in case _name is an empty string if not _name: _name = _elem.title if _name not in titles: titles[_name] = _elem.title toc.append((0, _name, unique_id, parent)) _parse_toc(epub_book.toc) self.notifier.debug("TOC structure: \n{}".format( pprint.pformat(toc, indent=4))) now = datetime.datetime.utcnow().replace(tzinfo=utc) default_status = get_default_book_status() stat = models.BookStatus.objects.filter(book=book, name=default_status)[0] # assign cover image if there is one cover_image = get_cover_image(epub_book) if cover_image: self._set_cover(book, cover_image) # import all images in the EPUB for image in epub_book.get_items_of_type(ebooklib.ITEM_IMAGE): if image == cover_image: continue if not self.delegate.should_import_image(image): continue name = os.path.normpath(image.file_name) att = models.Attachment(book=book, version=book.version, status=stat) with ContentFile(image.get_content()) as content_file: attName, attExt = os.path.splitext(os.path.basename(name)) att.attachment.save('{}{}'.format(booktype_slugify(attName), attExt), content_file, save=False) att.save() self._attachments[name] = att self.notifier.debug("Imported image: {} -> {}".format(image, att)) # URL titles assigned so far url_titles = [] def _make_url_title(title, i=0): url_title = booktype_slugify(title) if i > 0: url_title += "_" + str(i) if url_title not in url_titles: url_titles.append(url_title) return url_title else: return _make_url_title(title, i + 1) # import all document items from the EPUB for document in epub_book.get_items_of_type(ebooklib.ITEM_DOCUMENT): # Nav and Cover are not imported if not document.is_chapter(): continue if not self.delegate.should_import_document(document): continue name = os.path.normpath(document.file_name) title = '' # maybe this part has to go to the plugin # but you can not get title from <title> if name in titles: title = titles[name] else: title = convert_file_name(name) if title.rfind('.') != -1: title = title[:title.rfind('.')] title = title.replace('.', '') url_title = _make_url_title(title) content = self._create_content(document, title) chapter = models.Chapter(book=book, version=book.version, url_title=url_title, title=title, status=stat, content=content, created=now, modified=now) chapter.save() # time to save revisions correctly history = logChapterHistory(chapter=chapter, content=chapter.content, user=book.owner, comment='', revision=chapter.revision) if history: logBookHistory(book=book, version=book.version, chapter=chapter, chapter_history=history, user=book.owner, kind='chapter_create') self._chapters[name] = chapter self.notifier.debug("Imported chapter: {} -> {}".format( document, chapter)) # fix links to chapters for file_name, chapter in self._chapters.iteritems(): self._fix_links(chapter, base_path=os.path.dirname(file_name)) # create TOC objects self._make_toc(book, toc)
def _fix_links(self, chapter, base_path): """ Fixes internal links so they point to chapter URLs """ try: tree = ebooklib.utils.parse_html_string(chapter.content) except: return body = tree.find('body') if body is None: return to_save = False for anchor in body.iter('a'): href = anchor.get('href') if href is None: continue urlp = urlparse.urlparse(href) name = os.path.normpath( os.path.join(base_path, urllib.unquote(urlp.path))) if name in self._chapters: title = self._chapters[name].url_title fixed_href = urlparse.urljoin(href, '../{}/'.format(title)) if urlp.fragment: fixed_href = "{}#{}".format(fixed_href, urlp.fragment) anchor.set('href', fixed_href) to_save = True for image in body.iter('img'): src = image.get('src') if src is None: continue urlp = urlparse.urlparse(src) name = os.path.normpath( os.path.join(base_path, urllib.unquote(urlp.path))) if urlp.netloc: continue if name in self._attachments: file_name = os.path.basename( self._attachments[name].attachment.name) attName, attExt = os.path.splitext(file_name) fixed_src = urllib.quote('static/{}{}'.format( booktype_slugify(attName), attExt)) image.set('src', fixed_src) to_save = True if to_save: chapter.content = etree.tostring(tree, pretty_print=True, encoding='utf-8', xml_declaration=True) chapter.save()
def importBookFromFile(user, zname, createTOC=False, **extraOptions): """Create a new book from a bookizip filename""" from booki.utils.log import logChapterHistory # unzip it zf = zipfile.ZipFile(zname) # load info.json info = json.loads(zf.read('info.json')) logWarning("Loaded json file %r" % info) metadata = info['metadata'] manifest = info['manifest'] TOC = info['TOC'] if extraOptions.get('book_title', None): bookTitle = extraOptions['book_title'] else: bookTitle = get_metadata(metadata, 'title', ns=DC)[0] bookTitle = makeTitleUnique(bookTitle) logWarning("Chose unique book title %r" % bookTitle) if extraOptions.get('book_url', None): bookURL = extraOptions['book_url'] else: bookURL = None book = create_book(user, bookTitle, status = "new", bookURL = bookURL) if extraOptions.get("hidden"): book.hidden = True book.save() # this is for Table of Contents p = re.compile('\ssrc="(.*)"') # what if it does not have status "new" stat = models.BookStatus.objects.filter(book=book, name="new")[0] chapters = getChaptersFromTOC(TOC) n = len(chapters) + 1 #is +1 necessary? now = datetime.datetime.now() for chapterName, chapterFile, is_section in chapters: urlName = booktype_slugify(chapterName) if is_section: # create section if createTOC: c = models.BookToc(book = book, version = book.version, name = chapterName, chapter = None, weight = n, typeof = 2) c.save() n -= 1 else: # create chapter # check if i can open this file at all content = zf.read(chapterFile) #content = p.sub(r' src="../\1"', content) chapter = models.Chapter(book = book, version = book.version, url_title = urlName, title = chapterName, status = stat, content = content, created = now, modified = now) chapter.save() history = logChapterHistory(chapter = chapter, content = content, user = user, comment = "", revision = chapter.revision) if createTOC: c = models.BookToc(book = book, version = book.version, name = chapterName, chapter = chapter, weight = n, typeof = 1) c.save() n -= 1 stat = models.BookStatus.objects.filter(book=book, name="new")[0] from django.core.files import File for item in manifest.values(): if item["mimetype"] != 'text/html': attachmentName = item['url'] if attachmentName.startswith("static/"): att = models.Attachment(book = book, version = book.version, status = stat) s = zf.read(attachmentName) f = StringIO(s) f2 = File(f) f2.size = len(s) att.attachment.save(os.path.basename(attachmentName), f2, save=False) att.save() f.close() # metadata for namespace in metadata: # namespace is something like "http://purl.org/dc/elements/1.1/" or "" # in the former case, preepend it to the name, in {}. ns = ('{%s}' % namespace if namespace else '') for keyword, schemes in metadata[namespace].iteritems(): for scheme, values in schemes.iteritems(): #schema, if it is set, describes the value's format. #for example, an identifier might be an ISBN. sc = ('{%s}' % scheme if scheme else '') key = "%s%s%s" % (ns, keyword, sc) for v in values: if not v: continue try: info = models.Info(book=book, name=key) if len(v) >= 2500: info.value_text = v info.kind = 2 else: info.value_string = v info.kind = 0 info.save() except: # For now just ignore any kind of error here. # Considering we don't handle metadata as we # should it is not such a problem. pass zf.close() return book
def exportBook(book_version): from booki import bookizip import time starttime = time.time() (zfile, zname) = tempfile.mkstemp() spine = [] toc_top = [] toc_current = toc_top waiting_for_url = [] info = { "version": 1, "TOC": toc_top, "spine": spine, "metadata": _format_metadata(book_version.book), "manifest": {} } bzip = bookizip.BookiZip(zname, info=info) chapter_n = 1 for i, chapter in enumerate( models.BookToc.objects.filter( version=book_version).order_by("-weight")): if chapter.chapter: # It's a real chapter! With content! try: content = _fix_content(book_version.book, chapter, chapter_n) except: continue chapter_n += 1 ID = "ch%03d_%s" % (i, chapter.chapter.url_title.encode('utf-8')) filename = ID + '.html' toc_current.append({ "title": chapter.chapter.title, "url": filename, "type": "chapter", "role": "text" }) # If this is the first chapter in a section, lend our url # to the section, which has no content and thus no url of # its own. If this section was preceded by an empty # section, it will be waiting too, hence "while" rather # than "if". while waiting_for_url: section = waiting_for_url.pop() section["url"] = filename bzip.add_to_package(ID, filename, content, "text/html") spine.append(ID) else: #A new top level section. title = chapter.name.encode("utf-8") ID = "s%03d_%s" % (i, booktype_slugify(unicode(title))) toc_current = [] section = { "title": title, "url": '', "type": "booki-section", "children": toc_current } toc_top.append(section) waiting_for_url.append(section) #Attachments are images (and perhaps more). They do not know #whether they are currently in use, or what chapter they belong #to, so we add them all. #XXX scan for img links while adding chapters, and only add those. for i, attachment in enumerate( models.Attachment.objects.filter(version=book_version)): try: f = open(attachment.attachment.name, "rb") blob = f.read() f.close() except (IOError, OSError), e: msg = "couldn't read attachment %s" % e logWarning(msg) continue fn = os.path.basename(attachment.attachment.name.encode("utf-8")) ID = "att%03d_%s" % (i, booktype_slugify(unicode(fn))) if '.' in fn: _, ext = fn.rsplit('.', 1) mediatype = bookizip.MEDIATYPES.get(ext.lower(), bookizip.MEDIATYPES[None]) else: mediatype = bookizip.MEDIATYPES[None] bzip.add_to_package(ID, "static/%s" % fn, blob, mediatype)
def post(self, request, *args, **kwargs): # TODO test it and cover with tests book_security = BookSecurity(request.user, self._get_book()) user = request.user can_upload_attachment = book_security.has_perm( 'edit.upload_attachment') if not user.is_superuser and not can_upload_attachment and self._book.owner != user: raise PermissionDenied stat = BookStatus.objects.filter(book=self._book)[0] if 'file' not in request.FILES: raise ValidationError({'file': ['"file" is required.']}) file_data = request.FILES['file'] attname, attext = os.path.splitext(file_data.name) available_extensions = ('jpg', 'png', 'jpeg', 'gif') if attext.rsplit('.', 1)[-1].lower() not in available_extensions: raise ValidationError({ 'file': [ 'Not supported extension. Available extensions: {}'.format( ' '.join(available_extensions)) ] }) with transaction.atomic(): att = Attachment( version=self._book.version, # must remove this reference created=datetime.datetime.now(), book=self._book, status=stat) att.save() att.attachment.save('{}{}'.format(booktype_slugify(attname), attext), file_data, save=False) att.save() # notificatoin message channel_name = "/booktype/book/{}/{}/".format( self._book.id, self._book.version.get_version()) clnts = sputnik.smembers( "sputnik:channel:{}:channel".format(channel_name)) message = { 'channel': channel_name, 'command': 'notification', 'message': 'notification_new_attachment_uploaded', 'username': self.request.user.username, 'message_args': (att.get_name(), ) } for c in clnts: if c.strip() != '': sputnik.push("ses:%s:messages" % c, json.dumps(message)) # response serializer_instance = self.serializer_class(att) return Response(serializer_instance.data, status=status.HTTP_201_CREATED)
def clean_url_title(self): url_title = self.cleaned_data['url_title'] if not url_title: return misc.booktype_slugify(self.cleaned_data['title']) return url_title
def _get_section_key(self, title, count): return 'section_%s_%s' % (booktype_slugify(title), count)
def _fix_links(self, chapter, base_path): """ Fixes internal links so they point to chapter URLs """ try: tree = ebooklib.utils.parse_html_string(chapter.content) except: return body = tree.find('body') if body is None: return to_save = False for anchor in body.iter('a'): href = anchor.get('href') if href is None: continue urlp = urlparse.urlparse(href) name = os.path.normpath( os.path.join(base_path, urllib.unquote(urlp.path))) if name in self._chapters: title = self._chapters[name].url_title fixed_href = urlparse.urljoin(href, '../{}/'.format(title)) if urlp.fragment: fixed_href = "{}#{}".format(fixed_href, urlp.fragment) anchor.set('href', fixed_href) to_save = True for image in body.iter('img'): src = image.get('src') if src is None: continue urlp = urlparse.urlparse(src) name = os.path.normpath( os.path.join(base_path, urllib.unquote(urlp.path))) if urlp.netloc: continue if name in self._attachments: file_name = os.path.basename( self._attachments[name].attachment.name) attName, attExt = os.path.splitext(file_name) fixed_src = urllib.quote( 'static/{}{}'.format(booktype_slugify(attName), attExt)) image.set('src', fixed_src) to_save = True if to_save: chapter.content = etree.tostring( tree, pretty_print=True, encoding='utf-8', xml_declaration=True ) chapter.save()
def _import_chapters(self, book, chapters): now = datetime.datetime.now() default_status = get_default_book_status() stat = models.BookStatus.objects.filter(book=book, name=default_status)[0] n = 100 for chapter_title, chapter_content in chapters: if len(chapter_title) > 100: chapter_title = u'{}...'.format(chapter_title[:100]) if chapter_title == '': chapter_title = _('Title Page') if n == 100 else _('Title') chapter_n = 0 possible_title = chapter_title while True: does_exists = models.Chapter.objects.filter( book=book, version=book.version, url_title=booktype_slugify(possible_title)).exists() if does_exists: chapter_n += 1 possible_title = u'{} - {}'.format(chapter_title, chapter_n) else: break if chapter_content[6:-8].strip() == '': continue _content = self._parse_chapter(chapter_content) try: chapter_content = unidecode(_content)[6:-8] except UnicodeDecodeError: chapter_content = _content.decode('utf-8', errors='ignore')[6:-8] except Exception as err: chapter_content = 'Error parsing chapter content' logger.exception( "Error while decoding chapter content {0}".format(err)) chapter = models.Chapter( book=book, version=book.version, url_title=booktype_slugify(possible_title), title=possible_title, status=stat, content=chapter_content, created=now, modified=now) chapter.save() toc_item = models.BookToc(book=book, version=book.version, name=chapter.title, chapter=chapter, weight=n, typeof=1) toc_item.save() n -= 1 self._save_history_records(book, chapter)
def _import_chapters(self, book, chapters): now = datetime.datetime.now() stat = models.BookStatus.objects.filter(book=book, name="new")[0] n = 100 for chapter_title, chapter_content in chapters: if len(chapter_title) > 100: chapter_title = u'{}...'.format(chapter_title[:100]) if chapter_title == '': if n == 100: chapter_title = _('Title Page') else: chapter_title = _('Title') chapter_n = 0 possible_title = chapter_title while True: does_exists = models.Chapter.objects.filter( book=book, version=book.version, url_title=booktype_slugify(possible_title) ).exists() if does_exists: chapter_n += 1 possible_title = u'{} - {}'.format( chapter_title, chapter_n) else: break if chapter_content[6:-8].strip() == '': continue chapter_content = unidecode(self._parse_chapter(chapter_content)) chapter = models.Chapter( book=book, version=book.version, url_title=booktype_slugify(possible_title), title=possible_title, status=stat, content=chapter_content[6:-8], created=now, modified=now ) chapter.save() toc_item = models.BookToc( book=book, version=book.version, name=chapter.title, chapter=chapter, weight=n, typeof=1 ) toc_item.save() n -= 1 # time to save revisions correctly history = logChapterHistory( chapter=chapter, content=chapter.content, user=book.owner, comment='', revision=chapter.revision ) if history: logBookHistory( book=book, version=book.version, chapter=chapter, chapter_history=history, user=book.owner, kind='chapter_create' )
def post(self, request, *args, **kwargs): # TODO test it and cover with tests book_security = BookSecurity(request.user, self._get_book()) user = request.user can_upload_attachment = book_security.has_perm('edit.upload_attachment') if not user.is_superuser and not can_upload_attachment and self._book.owner != user: raise PermissionDenied stat = BookStatus.objects.filter(book=self._book)[0] if 'file' not in request.FILES: raise ValidationError({'file': ['"file" is required.']}) file_data = request.FILES['file'] attname, attext = os.path.splitext(file_data.name) available_extensions = ('jpg', 'png', 'jpeg', 'gif') if attext.rsplit('.', 1)[-1].lower() not in available_extensions: raise ValidationError({'file': [ 'Not supported extension. Available extensions: {}'.format( ' '.join(available_extensions)) ]}) with transaction.atomic(): att = Attachment( version=self._book.version, # must remove this reference created=datetime.datetime.now(), book=self._book, status=stat ) att.save() att.attachment.save( '{}{}'.format(booktype_slugify(attname), attext), file_data, save=False ) att.save() # notificatoin message channel_name = "/booktype/book/{}/{}/".format(self._book.id, self._book.version.get_version()) clnts = sputnik.smembers( "sputnik:channel:{}:channel".format(channel_name)) message = { 'channel': channel_name, 'command': 'notification', 'message': 'notification_new_attachment_uploaded', 'username': self.request.user.username, 'message_args': (att.get_name(),) } for c in clnts: if c.strip() != '': sputnik.push("ses:%s:messages" % c, json.dumps(message)) # response serializer_instance = self.serializer_class(att) return Response(serializer_instance.data, status=status.HTTP_201_CREATED)
def _import_chapters(self, book, chapters): now = datetime.datetime.now() stat = models.BookStatus.objects.filter(book=book, name="new")[0] n = 100 for chapter_title, chapter_content in chapters: if len(chapter_title) > 100: chapter_title = u'{}...'.format(chapter_title[:100]) if chapter_title == '': if n == 100: chapter_title = _('Title Page') else: chapter_title = _('Title') chapter_n = 0 possible_title = chapter_title while True: does_exists = models.Chapter.objects.filter( book=book, version=book.version, url_title=booktype_slugify(possible_title)).exists() if does_exists: chapter_n += 1 possible_title = u'{} - {}'.format(chapter_title, chapter_n) else: break if chapter_content[6:-8].strip() == '': continue chapter_content = self._parse_chapter(chapter_content) chapter = models.Chapter( book=book, version=book.version, url_title=booktype_slugify(possible_title), title=possible_title, status=stat, content=chapter_content[6:-8], created=now, modified=now) chapter.save() toc_item = models.BookToc(book=book, version=book.version, name=chapter.title, chapter=chapter, weight=n, typeof=1) toc_item.save() n -= 1 # time to save revisions correctly history = logChapterHistory(chapter=chapter, content=chapter.content, user=book.owner, comment='', revision=chapter.revision) if history: logBookHistory(book=book, version=book.version, chapter=chapter, chapter_history=history, user=book.owner, kind='chapter_create')
def form_valid(self, form): logger.debug('ImporterView::form_valid') book_file = self.request.FILES['book_file'] ext = self.file_extension(book_file.name) logger.debug('ImporterView::Importing file extension is "{}".'.format( ext.encode('utf8'))) temp_file = tempfile.NamedTemporaryFile(prefix='importing-', suffix='%s' % ext, delete=False) temp_file = open(temp_file.name, 'wb+') logger.debug('ImporterView::Saving temporary file {}.'.format( temp_file.name.encode('utf8'))) for chunk in book_file.chunks(): temp_file.write(chunk) temp_file.close() temp_file = temp_file.name default_book_title = self.get_default_title(temp_file, ext) book_title = form.cleaned_data.get('book_title', default_book_title) logger.debug( 'ImporterView::book_title="{}"" default_book_title="{}".'.format( book_title.encode('utf8'), default_book_title.encode('utf8'))) # in case book title in form is empty string if len(book_title) == 0: book_title = default_book_title if not check_book_availability(book_title): registered = Book.objects.filter( title__startswith=book_title).count() book_title = '%s %s' % (book_title, registered) logger.debug( 'ImporterView::Checking book availability: "{}".'.format( book_title.encode('utf8'))) book_url = booktype_slugify(book_title) book = create_book(self.request.user, book_title, book_url=book_url) logger.debug( 'ImporterView::Book created with url title="{}".'.format(book_url)) # check if book will be hidden and set to book book_hidden = form.cleaned_data.get('hidden') if book_hidden: book.hidden = book_hidden book.save() logger.debug('ImporterView::Setting book hidden.') else: logger.debug('ImporterView::Setting book visible.') notifier = CollectNotifier() delegate = Delegate() response = {} try: book_importer = self.get_importer(ext) except KeyError: logger.error('ImporterView::No importer for this extension') response_data = { 'errors': [_('Extension not supported!')], } return self.render_json_response(response_data) try: book_importer(temp_file, book, notifier=notifier, delegate=delegate) logger.debug('ImporterView::Book imported.') response['url'] = reverse('reader:infopage', args=[book.url_title]) except Exception as e: logger.error( 'ImporterView::Some kind of error while importing book.') logger.exception(e) notifier.errors.append(str(e)) response['infos'] = notifier.infos response['warnings'] = notifier.warnings response['errors'] = notifier.errors return self.render_json_response(response)
def _import_book(self, epub_book, book): titles = {} toc = [] def _parse_toc(elements, parent=None): for _elem in elements: # used later to get parent of an elem unique_id = uuid.uuid4().hex if isinstance(_elem, tuple): toc.append((1, _elem[0].title, unique_id, parent)) _parse_toc(_elem[1]) elif isinstance(_elem, ebooklib.epub.Section): pass elif isinstance(_elem, ebooklib.epub.Link): _urlp = urlparse.urlparse(_elem.href) _name = os.path.normpath(urllib.unquote(_urlp.path)) # check in case _name is an empty string if not _name: _name = _elem.title if _name not in titles: titles[_name] = _elem.title toc.append((0, _name, unique_id, parent)) _parse_toc(epub_book.toc) self.notifier.debug( "TOC structure: \n{}".format(pprint.pformat(toc, indent=4))) now = datetime.datetime.utcnow().replace(tzinfo=utc) stat = models.BookStatus.objects.filter(book=book, name="new")[0] # assign cover image if there is one cover_image = get_cover_image(epub_book) if cover_image: self._set_cover(book, cover_image) # import all images in the EPUB for image in epub_book.get_items_of_type(ebooklib.ITEM_IMAGE): if image == cover_image: continue if not self.delegate.should_import_image(image): continue name = os.path.normpath(image.file_name) att = models.Attachment(book=book, version=book.version, status=stat) with ContentFile(image.get_content()) as content_file: attName, attExt = os.path.splitext(os.path.basename(name)) att.attachment.save( '{}{}'.format(booktype_slugify(attName), attExt), content_file, save=False ) att.save() self._attachments[name] = att self.notifier.debug("Imported image: {} -> {}".format(image, att)) # URL titles assigned so far url_titles = [] def _make_url_title(title, i=0): url_title = booktype_slugify(title) if i > 0: url_title += "_" + str(i) if url_title not in url_titles: url_titles.append(url_title) return url_title else: return _make_url_title(title, i + 1) # import all document items from the EPUB for document in epub_book.get_items_of_type(ebooklib.ITEM_DOCUMENT): # Nav and Cover are not imported if not document.is_chapter(): continue if not self.delegate.should_import_document(document): continue name = os.path.normpath(document.file_name) title = '' # maybe this part has to go to the plugin # but you can not get title from <title> if name in titles: title = titles[name] else: title = convert_file_name(name) if title.rfind('.') != -1: title = title[:title.rfind('.')] title = title.replace('.', '') url_title = _make_url_title(title) content = self._create_content(document, title) chapter = models.Chapter( book=book, version=book.version, url_title=url_title, title=title, status=stat, content=content, created=now, modified=now ) chapter.save() # time to save revisions correctly history = logChapterHistory( chapter=chapter, content=chapter.content, user=book.owner, comment='', revision=chapter.revision ) if history: logBookHistory( book=book, version=book.version, chapter=chapter, chapter_history=history, user=book.owner, kind='chapter_create' ) self._chapters[name] = chapter self.notifier.debug( "Imported chapter: {} -> {}".format(document, chapter)) # fix links to chapters for file_name, chapter in self._chapters.iteritems(): self._fix_links(chapter, base_path=os.path.dirname(file_name)) # create TOC objects self._make_toc(book, toc)
def _toc(depth, toc_items, parent=None, toc_setting=None): items = [] sec_count = 1 for toc_item in toc_items: # SECTIONS if isinstance(toc_item[1], list): section_title, chapters = toc_item url_title = booktype_slugify(section_title) # let's build a section key and try to get settings for current section section_key = SectionsSettingsPlugin.build_section_key( url_title, sec_count) section_settings = json.loads( settings_dict.get(section_key, '{}')) toc_setting = section_settings.get('toc', {}).get(self.name, '') # jump to next item (continue) if the whole section should be hidden show_in_outputs = section_settings.get( 'show_in_outputs', {}) show_section_in_current_converter = show_in_outputs.get( self.name, True) if not show_section_in_current_converter: continue toc_item = TocItem({ 'type': 'section', 'level': depth, 'title': section_title, 'url_title': url_title, 'show_in_toc': 'hide_section' not in toc_setting }) items.append(toc_item) items += _toc(depth + 1, chapters, section_title, toc_setting) sec_count += 1 # CHAPTERS else: chapter_title, chapter_href = toc_item chapter_item = book.get_item_with_href(chapter_href) content = self._get_chapter_content(chapter_item) content = self._fix_horrible_mpdf(content) href_filename, file_extension = os.path.splitext( chapter_href) if not parent: toc_setting = '' toc_item = TocItem({ 'type': 'chapter', 'level': depth, 'title': chapter_title, 'url_title': booktype_slugify(chapter_title), 'href': chapter_href, 'href_filename': href_filename, 'content': content, 'show_in_toc': 'hide_chapters' not in toc_setting }) items.append(toc_item) return items
def importBookFromFile(user, zname, createTOC=False, **extraOptions): """Create a new book from a bookizip filename""" from booki.utils.log import logChapterHistory # unzip it zf = zipfile.ZipFile(zname) # load info.json info = json.loads(zf.read('info.json')) logWarning("Loaded json file %r" % info) metadata = info['metadata'] manifest = info['manifest'] TOC = info['TOC'] if extraOptions.get('book_title', None): bookTitle = extraOptions['book_title'] else: bookTitle = get_metadata(metadata, 'title', ns=DC)[0] bookTitle = makeTitleUnique(bookTitle) logWarning("Chose unique book title %r" % bookTitle) if extraOptions.get('book_url', None): bookURL = extraOptions['book_url'] else: bookURL = None book = create_book(user, bookTitle, status="new", bookURL=bookURL) if extraOptions.get("hidden"): book.hidden = True book.save() # this is for Table of Contents p = re.compile('\ssrc="(.*)"') # what if it does not have status "new" stat = models.BookStatus.objects.filter(book=book, name="new")[0] chapters = getChaptersFromTOC(TOC) n = len(chapters) + 1 #is +1 necessary? now = datetime.datetime.now() for chapterName, chapterFile, is_section in chapters: urlName = booktype_slugify(chapterName) if is_section: # create section if createTOC: c = models.BookToc(book=book, version=book.version, name=chapterName, chapter=None, weight=n, typeof=2) c.save() n -= 1 else: # create chapter # check if i can open this file at all content = zf.read(chapterFile) #content = p.sub(r' src="../\1"', content) chapter = models.Chapter(book=book, version=book.version, url_title=urlName, title=chapterName, status=stat, content=content, created=now, modified=now) chapter.save() history = logChapterHistory(chapter=chapter, content=content, user=user, comment="", revision=chapter.revision) if createTOC: c = models.BookToc(book=book, version=book.version, name=chapterName, chapter=chapter, weight=n, typeof=1) c.save() n -= 1 stat = models.BookStatus.objects.filter(book=book, name="new")[0] from django.core.files import File for item in manifest.values(): if item["mimetype"] != 'text/html': attachmentName = item['url'] if attachmentName.startswith("static/"): att = models.Attachment(book=book, version=book.version, status=stat) s = zf.read(attachmentName) f = StringIO(s) f2 = File(f) f2.size = len(s) att.attachment.save(os.path.basename(attachmentName), f2, save=False) att.save() f.close() # metadata for namespace in metadata: # namespace is something like "http://purl.org/dc/elements/1.1/" or "" # in the former case, preepend it to the name, in {}. ns = ('{%s}' % namespace if namespace else '') for keyword, schemes in metadata[namespace].iteritems(): for scheme, values in schemes.iteritems(): #schema, if it is set, describes the value's format. #for example, an identifier might be an ISBN. sc = ('{%s}' % scheme if scheme else '') key = "%s%s%s" % (ns, keyword, sc) for v in values: if not v: continue try: info = models.Info(book=book, name=key) if len(v) >= 2500: info.value_text = v info.kind = 2 else: info.value_string = v info.kind = 0 info.save() except: # For now just ignore any kind of error here. # Considering we don't handle metadata as we # should it is not such a problem. pass zf.close() return book
def form_valid(self, form): logger.debug('ImporterView::form_valid') book_file = self.request.FILES['book_file'] ext = self.file_extension(book_file.name) logger.debug('ImporterView::Importing file extension is "{}".'.format(ext.encode('utf8'))) temp_file = tempfile.NamedTemporaryFile( prefix='importing-', suffix='%s' % ext, delete=False) temp_file = open(temp_file.name, 'wb+') logger.debug('ImporterView::Saving temporary file {}.'.format(temp_file.name.encode('utf8'))) for chunk in book_file.chunks(): temp_file.write(chunk) temp_file.close() temp_file = temp_file.name default_book_title = self.get_default_title(temp_file, ext) book_title = form.cleaned_data.get('book_title', default_book_title) logger.debug('ImporterView::book_title="{}"" default_book_title="{}".'.format(book_title.encode('utf8'), default_book_title.encode('utf8'))) # in case book title in form is empty string if len(book_title) == 0: book_title = default_book_title if not check_book_availability(book_title): registered = Book.objects.filter( title__startswith=book_title).count() book_title = '%s %s' % (book_title, registered) logger.debug('ImporterView::Checking book availability: "{}".'.format(book_title.encode('utf8'))) book_url = booktype_slugify(book_title) book = create_book( self.request.user, book_title, book_url=book_url) logger.debug('ImporterView::Book created with url title="{}".'.format(book_url)) # check if book will be hidden and set to book book_hidden = form.cleaned_data.get('hidden') if book_hidden: book.hidden = book_hidden book.save() logger.debug('ImporterView::Setting book hidden.') else: logger.debug('ImporterView::Setting book visible.') notifier = CollectNotifier() delegate = Delegate() response = {} try: book_importer = self.get_importer(ext) except KeyError: logger.error('ImporterView::No importer for this extension') response_data = { 'errors': [_('Extension not supported!')], } return self.render_json_response(response_data) try: book_importer( temp_file, book, notifier=notifier, delegate=delegate ) logger.debug('ImporterView::Book imported.') response['url'] = reverse('reader:infopage', args=[book.url_title]) except Exception as e: logger.error('ImporterView::Some kind of error while importing book.') logger.exception(e) notifier.errors.append(str(e)) response['infos'] = notifier.infos response['warnings'] = notifier.warnings response['errors'] = notifier.errors return self.render_json_response(response)
def books(request): books = [] for title in request.param: books.append( BookFactory(title=title, url_title=booktype_slugify(title))) return books
def exportBook(book_version): from booki import bookizip import time starttime = time.time() (zfile, zname) = tempfile.mkstemp() spine = [] toc_top = [] toc_current = toc_top waiting_for_url = [] info = { "version": 1, "TOC": toc_top, "spine": spine, "metadata": _format_metadata(book_version.book), "manifest": {} } bzip = bookizip.BookiZip(zname, info=info) chapter_n = 1 for i, chapter in enumerate(models.BookToc.objects.filter(version=book_version).order_by("-weight")): if chapter.chapter: # It's a real chapter! With content! try: content = _fix_content(book_version.book, chapter, chapter_n) except: continue chapter_n += 1 ID = "ch%03d_%s" % (i, chapter.chapter.url_title.encode('utf-8')) filename = ID + '.html' toc_current.append({"title": chapter.chapter.title, "url": filename, "type": "chapter", "role": "text" }) # If this is the first chapter in a section, lend our url # to the section, which has no content and thus no url of # its own. If this section was preceded by an empty # section, it will be waiting too, hence "while" rather # than "if". while waiting_for_url: section = waiting_for_url.pop() section["url"] = filename bzip.add_to_package(ID, filename, content, "text/html") spine.append(ID) else: #A new top level section. title = chapter.name.encode("utf-8") ID = "s%03d_%s" % (i, booktype_slugify(unicode(title))) toc_current = [] section = {"title": title, "url": '', "type": "booki-section", "children": toc_current } toc_top.append(section) waiting_for_url.append(section) #Attachments are images (and perhaps more). They do not know #whether they are currently in use, or what chapter they belong #to, so we add them all. #XXX scan for img links while adding chapters, and only add those. for i, attachment in enumerate(models.Attachment.objects.filter(version=book_version)): try: f = open(attachment.attachment.name, "rb") blob = f.read() f.close() except (IOError, OSError), e: msg = "couldn't read attachment %s" % e logWarning(msg) continue fn = os.path.basename(attachment.attachment.name.encode("utf-8")) ID = "att%03d_%s" % (i, booktype_slugify(unicode(fn))) if '.' in fn: _, ext = fn.rsplit('.', 1) mediatype = bookizip.MEDIATYPES.get(ext.lower(), bookizip.MEDIATYPES[None]) else: mediatype = bookizip.MEDIATYPES[None] bzip.add_to_package(ID, "static/%s" % fn, blob, mediatype)