pull/635/head
bodybybuddha 6 years ago
commit 93a9f65198

@ -123,9 +123,11 @@ def pdf_preview(tmp_file_path, tmp_dir):
def get_versions():
if not use_generic_pdf_cover:
IVersion=ImageVersion.MAGICK_VERSION
IVersion = ImageVersion.MAGICK_VERSION
WVersion = ImageVersion.VERSION
else:
IVersion = _(u'not installed')
WVersion = _(u'not installed')
if use_pdf_meta:
PVersion='v'+PyPdfVersion
else:
@ -134,4 +136,4 @@ def get_versions():
XVersion = 'v'+'.'.join(map(str, lxmlversion))
else:
XVersion = _(u'not installed')
return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion}
return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion, 'Wand Version': WVersion}

@ -45,5 +45,5 @@ def versioncheck():
elif ub.config.config_ebookconverter == 2:
return versionCalibre()
else:
return {'ebook_converter':''}
return {'ebook_converter':_(u'not configured')}

@ -149,19 +149,19 @@ def getDrive(drive=None, gauth=None):
drive.auth.Refresh()
return drive
def listRootFolders(drive=None):
drive = getDrive(drive)
def listRootFolders():
drive = getDrive(Gdrive.Instance().drive)
folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false"
fileList = drive.ListFile({'q': folder}).GetList()
return fileList
def getEbooksFolder(drive=None):
def getEbooksFolder(drive):
return getFolderInFolder('root',config.config_google_drive_folder,drive)
def getFolderInFolder(parentId, folderName,drive=None):
drive = getDrive(drive)
def getFolderInFolder(parentId, folderName, drive):
# drive = getDrive(drive)
query=""
if folderName:
query = "title = '%s' and " % folderName.replace("'", "\\'")
@ -190,7 +190,6 @@ def getEbooksFolderId(drive=None):
def getFile(pathId, fileName, drive):
# drive = getDrive(Gdrive.Instance().drive)
metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'"))
fileList = drive.ListFile({'q': metaDataFile}).GetList()
@ -200,8 +199,8 @@ def getFile(pathId, fileName, drive):
return fileList[0]
def getFolderId(path, drive=None):
drive = getDrive(drive)
def getFolderId(path, drive):
# drive = getDrive(drive)
currentFolderId = getEbooksFolderId(drive)
sqlCheckPath = path if path[-1] == '/' else path + '/'
storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
@ -249,7 +248,7 @@ def getFileFromEbooksFolder(path, fileName):
return None
def copyDriveFileRemote(drive, origin_file_id, copy_title):
'''def copyDriveFileRemote(drive, origin_file_id, copy_title):
drive = getDrive(drive)
copied_file = {'title': copy_title}
try:
@ -258,7 +257,7 @@ def copyDriveFileRemote(drive, origin_file_id, copy_title):
return drive.CreateFile({'id': file_data['id']})
except errors.HttpError as error:
print ('An error occurred: %s' % error)
return None
return None'''
# Download metadata.db from gdrive
@ -347,7 +346,6 @@ def uploadFileToEbooksFolder(destFile, f):
def watchChange(drive, channel_id, channel_type, channel_address,
channel_token=None, expiration=None):
# drive = getDrive(drive)
# Watch for all changes to a user's Drive.
# Args:
# service: Drive API service instance.
@ -390,8 +388,6 @@ def watchFile(drive, file_id, channel_id, channel_type, channel_address,
Raises:
apiclient.errors.HttpError: if http request to create channel fails.
"""
# drive = getDrive(drive)
body = {
'id': channel_id,
'type': channel_type,
@ -413,8 +409,6 @@ def stopChannel(drive, channel_id, resource_id):
Raises:
apiclient.errors.HttpError: if http request to create channel fails.
"""
# drive = getDrive(drive)
# service=drive.auth.service
body = {
'id': channel_id,
'resourceId': resource_id
@ -423,7 +417,6 @@ def stopChannel(drive, channel_id, resource_id):
def getChangeById (drive, change_id):
# drive = getDrive(drive)
# Print a single Change resource information.
#
# Args:
@ -454,11 +447,13 @@ def updateDatabaseOnEdit(ID,newPath):
storedPathName.path = newPath
session.commit()
# Deletes the hashes in database of deleted book
def deleteDatabaseEntry(ID):
session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete()
session.commit()
# Gets cover file from gdrive
def get_cover_via_gdrive(cover_path):
df = getFileFromEbooksFolder(cover_path, 'cover.jpg')

@ -0,0 +1,39 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class ReverseProxied(object):
"""Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is
different than what is used locally.
Code courtesy of: http://flask.pocoo.org/snippets/35/
In nginx:
location /myprefix {
proxy_pass http://127.0.0.1:8083;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /myprefix;
}
"""
def __init__(self, application):
self.app = application
def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ.get('PATH_INFO', '')
if path_info and path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]
scheme = environ.get('HTTP_X_SCHEME', '')
if scheme:
environ['wsgi.url_scheme'] = scheme
servr = environ.get('HTTP_X_FORWARDED_SERVER', '')
if servr:
environ['HTTP_HOST'] = servr
return self.app(environ, start_response)

@ -1,11 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from socket import error as SocketError
import sys
import os
import signal
import web
try:
from gevent.pywsgi import WSGIServer
@ -19,8 +19,6 @@ except ImportError:
from tornado import version as tornadoVersion
gevent_present = False
import web
class server:
@ -29,7 +27,7 @@ class server:
def __init__(self):
signal.signal(signal.SIGINT, self.killServer)
signal.signal(signal.SIGTERM, self.killServer)
signal.signal(signal.SIGTERM, self.killServer)
def start_gevent(self):
try:
@ -68,7 +66,8 @@ class server:
ssl_options=ssl)
http_server.listen(web.ub.config.config_port)
self.wsgiserver=IOLoop.instance()
self.wsgiserver.start() # wait for stop signal
self.wsgiserver.start()
# wait for stop signal
self.wsgiserver.close(True)
if self.restart == True:

@ -1173,7 +1173,7 @@ msgstr "Pfad zu Konvertertool"
#: cps/templates/config_edit.html:199
msgid "Location of Unrar binary"
msgstr "Ofad zum UnRar Programm"
msgstr "Pfad zum UnRar Programm"
#: cps/templates/config_edit.html:215 cps/templates/layout.html:82
#: cps/templates/login.html:4

@ -1,38 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
try:
from googleapiclient.errors import HttpError
except ImportError:
pass
try:
from goodreads.client import GoodreadsClient
goodreads_support = True
except ImportError:
goodreads_support = False
try:
import Levenshtein
levenshtein_support = True
except ImportError:
levenshtein_support = False
try:
from functools import reduce
except ImportError:
pass # We're not using Python 3
try:
import rarfile
rar_support=True
except ImportError:
rar_support=False
try:
from natsort import natsorted as sort
except ImportError:
sort=sorted # Just use regular sort then
# may cause issues with badly named pages in cbz/cbr files
import mimetypes
import logging
@ -41,6 +8,8 @@ from flask import (Flask, render_template, request, Response, redirect,
url_for, send_from_directory, make_response, g, flash,
abort, Markup)
from flask import __version__ as flaskVersion
from werkzeug import __version__ as werkzeugVersion
from jinja2 import __version__ as jinja2Version
import cache_buster
import ub
from ub import config
@ -72,6 +41,7 @@ import json
import datetime
from iso639 import languages as isoLanguages
from iso639 import __version__ as iso639Version
from pytz import __version__ as pytzVersion
from uuid import uuid4
import os.path
import sys
@ -81,10 +51,43 @@ from shutil import move, copyfile
import gdriveutils
import converter
import tempfile
import hashlib
from redirect import redirect_back
import time
import server
from reverseproxy import ReverseProxied
try:
from googleapiclient.errors import HttpError
except ImportError:
pass
try:
from goodreads.client import GoodreadsClient
goodreads_support = True
except ImportError:
goodreads_support = False
try:
import Levenshtein
levenshtein_support = True
except ImportError:
levenshtein_support = False
try:
from functools import reduce
except ImportError:
pass # We're not using Python 3
try:
import rarfile
rar_support=True
except ImportError:
rar_support=False
try:
from natsort import natsorted as sort
except ImportError:
sort=sorted # Just use regular sort then
# may cause issues with badly named pages in cbz/cbr files
try:
import cPickle
except ImportError:
@ -101,12 +104,10 @@ try:
except ImportError:
from flask_login.__about__ import __version__ as flask_loginVersion
current_milli_time = lambda: int(round(time.time() * 1000))
# Global variables
current_milli_time = lambda: int(round(time.time() * 1000))
gdrive_watch_callback_token = 'target=calibreweb-watch_files'
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
'fb2', 'html', 'rtf', 'odt'}
EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'html', 'rtf', 'odt'}
@ -114,15 +115,7 @@ EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit'
# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + (['rar','cbr'] if rar_support else []))
def md5(fname):
hash_md5 = hashlib.md5()
with open(fname, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
class ReverseProxied(object):
'''class ReverseProxied(object):
"""Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is
@ -157,7 +150,7 @@ class ReverseProxied(object):
servr = environ.get('HTTP_X_FORWARDED_SERVER', '')
if servr:
environ['HTTP_HOST'] = servr
return self.app(environ, start_response)
return self.app(environ, start_response)'''
# Main code
@ -1666,15 +1659,19 @@ def stats():
categorys = db.session.query(db.Tags).count()
series = db.session.query(db.Series).count()
versions = uploader.book_formats.get_versions()
versions['Babel'] = 'v'+babelVersion
versions['Sqlalchemy'] = 'v'+sqlalchemyVersion
versions['Flask'] = 'v'+flaskVersion
versions['Flask Login'] = 'v'+flask_loginVersion
versions['Flask Principal'] = 'v'+flask_principalVersion
versions['Iso 639'] = 'v'+iso639Version
versions['Requests'] = 'v'+requests.__version__
versions['pySqlite'] = 'v'+db.engine.dialect.dbapi.version
versions['Sqlite'] = 'v'+db.engine.dialect.dbapi.sqlite_version
versions['Babel'] = 'v' + babelVersion
versions['Sqlalchemy'] = 'v' + sqlalchemyVersion
versions['Werkzeug'] = 'v' + werkzeugVersion
versions['Jinja2'] = 'v' + jinja2Version
versions['Flask'] = 'v' + flaskVersion
versions['Flask Login'] = 'v' + flask_loginVersion
versions['Flask Principal'] = 'v' + flask_principalVersion
versions['Iso 639'] = 'v' + iso639Version
versions['pytz'] = 'v' + pytzVersion
versions['Requests'] = 'v' + requests.__version__
versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
versions.update(converter.versioncheck())
versions.update(server.Server.getNameVersion())
versions['Python'] = sys.version
@ -3359,24 +3356,19 @@ def reset_password(user_id):
return redirect(url_for('admin'))
@app.route("/admin/book/<int:book_id>", methods=['GET', 'POST'])
@login_required_if_no_ano
@edit_required
def edit_book(book_id):
# create the function for sorting...
def render_edit_book(book_id):
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
book = db.session.query(db.Books)\
.filter(db.Books.id == book_id).filter(common_filters()).first()
author_names = []
# Book not found
if not book:
flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
return redirect(url_for("index"))
for indx in range(0, len(book.languages)):
book.languages[indx].language_name = language_table[get_locale()][book.languages[indx].lang_code]
author_names = []
for authr in book.authors:
author_names.append(authr.name.replace('|', ','))
@ -3395,14 +3387,92 @@ def edit_book(book_id):
except Exception:
app.logger.warning(file.format.lower() + ' already removed from list.')
app.logger.debug('Allowed conversion formats: '+ ', '.join(allowed_conversion_formats))
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
title=_(u"edit metadata"), page="editbook",
conversion_formats=allowed_conversion_formats,
source_formats=valid_source_formats)
# Show form
if request.method != 'POST':
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
title=_(u"edit metadata"), page="editbook",
conversion_formats=allowed_conversion_formats,
source_formats=valid_source_formats)
def edit_cc_data(book_id, book, to_save):
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
for c in cc:
cc_string = "custom_column_" + str(c.id)
if not c.is_multiple:
if len(getattr(book, cc_string)) > 0:
cc_db_value = getattr(book, cc_string)[0].value
else:
cc_db_value = None
if to_save[cc_string].strip():
if c.datatype == 'bool':
if to_save[cc_string] == 'None':
to_save[cc_string] = None
else:
to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0
if to_save[cc_string] != cc_db_value:
if cc_db_value is not None:
if to_save[cc_string] is not None:
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
else:
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc)
else:
cc_class = db.cc_classes[c.id]
new_cc = cc_class(value=to_save[cc_string], book=book_id)
db.session.add(new_cc)
elif c.datatype == 'int':
if to_save[cc_string] == 'None':
to_save[cc_string] = None
if to_save[cc_string] != cc_db_value:
if cc_db_value is not None:
if to_save[cc_string] is not None:
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
else:
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc)
else:
cc_class = db.cc_classes[c.id]
new_cc = cc_class(value=to_save[cc_string], book=book_id)
db.session.add(new_cc)
else:
if c.datatype == 'rating':
to_save[cc_string] = str(int(float(to_save[cc_string]) * 2))
if to_save[cc_string].strip() != cc_db_value:
if cc_db_value is not None:
# remove old cc_val
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0:
db.session.delete(del_cc)
cc_class = db.cc_classes[c.id]
new_cc = db.session.query(cc_class).filter(
cc_class.value == to_save[cc_string].strip()).first()
# if no cc val is found add it
if new_cc is None:
new_cc = cc_class(value=to_save[cc_string].strip())
db.session.add(new_cc)
db.session.flush()
new_cc = db.session.query(cc_class).filter(
cc_class.value == to_save[cc_string].strip()).first()
# add cc value to book
getattr(book, cc_string).append(new_cc)
else:
if cc_db_value is not None:
# remove old cc_val
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0:
db.session.delete(del_cc)
else:
input_tags = to_save[cc_string].split(',')
input_tags = list(map(lambda it: it.strip(), input_tags))
modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session,
'custom')
return cc
def upload_single_file(request, book, book_id):
# Check and handle Uploaded file
if 'btn-upload-format' in request.files:
requested_file = request.files['btn-upload-format']
@ -3449,9 +3519,10 @@ def edit_book(book_id):
# Queue uploader info
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
helper.global_WorkerThread.add_upload(current_user.nickname,
helper.global_WorkerThread.add_upload(current_user.nickname,
"<a href=\"" + url_for('show_book', book_id=book.id) + "\">" + uploadText + "</a>")
def upload_cover(request, book):
if 'btn-upload-cover' in request.files:
requested_file = request.files['btn-upload-cover']
# check for empty request
@ -3477,15 +3548,37 @@ def edit_book(book_id):
except IOError:
flash(_(u"Cover-file is not a valid image file" % saved_filename), category="error")
return redirect(url_for('show_book', book_id=book.id))
to_save = request.form.to_dict()
@app.route("/admin/book/<int:book_id>", methods=['GET', 'POST'])
@login_required_if_no_ano
@edit_required
def edit_book(book_id):
# Show form
if request.method != 'POST':
return render_edit_book(book_id)
# create the function for sorting...
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
book = db.session.query(db.Books)\
.filter(db.Books.id == book_id).filter(common_filters()).first()
# Book not found
if not book:
flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
return redirect(url_for("index"))
upload_single_file(request, book, book_id)
upload_cover(request, book)
try:
to_save = request.form.to_dict()
# Update book
edited_books_id = set()
edited_books_id = None
#handle book title
if book.title != to_save["book_title"]:
if to_save["book_title"] == '':
to_save["book_title"] = _(u'unknown')
book.title = to_save["book_title"]
edited_books_id.add(book.id)
edited_books_id = book.id
# handle author(s)
input_authors = to_save["author_name"].split('&')
@ -3500,18 +3593,17 @@ def edit_book(book_id):
modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
if book.authors:
if author0_before_edit != book.authors[0].name:
edited_books_id.add(book.id)
edited_books_id = book.id
book.author_sort = helper.get_sorted_author(input_authors[0])
if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal()
error = False
for b in edited_books_id:
error = helper.update_dir_stucture(b, config.config_calibre_dir)
if edited_books_id:
error = helper.update_dir_stucture(edited_books_id, config.config_calibre_dir)
if error: # stop on error
flash(error, category="error")
break
if not error:
if to_save["cover_url"]:
@ -3554,7 +3646,6 @@ def edit_book(book_id):
# handle book languages
input_languages = to_save["languages"].split(',')
# input_languages = list(map(lambda it: it.strip().lower(), input_languages))
input_languages = [x.strip().lower() for x in input_languages if x != '']
input_l = []
invers_lang_table = [x.lower() for x in language_table[get_locale()].values()]
@ -3567,6 +3658,7 @@ def edit_book(book_id):
flash(_(u"%(langname)s is not a valid language", langname=lang), category="error")
modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
# handle book ratings
if to_save["rating"].strip():
old_rating = False
if len(book.ratings) > 0:
@ -3585,104 +3677,20 @@ def edit_book(book_id):
if len(book.ratings) > 0:
book.ratings.remove(book.ratings[0])
for c in cc:
cc_string = "custom_column_" + str(c.id)
if not c.is_multiple:
if len(getattr(book, cc_string)) > 0:
cc_db_value = getattr(book, cc_string)[0].value
else:
cc_db_value = None
if to_save[cc_string].strip():
if c.datatype == 'bool':
if to_save[cc_string] == 'None':
to_save[cc_string] = None
else:
to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0
if to_save[cc_string] != cc_db_value:
if cc_db_value is not None:
if to_save[cc_string] is not None:
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
else:
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc)
else:
cc_class = db.cc_classes[c.id]
new_cc = cc_class(value=to_save[cc_string], book=book_id)
db.session.add(new_cc)
elif c.datatype == 'int':
if to_save[cc_string] == 'None':
to_save[cc_string] = None
if to_save[cc_string] != cc_db_value:
if cc_db_value is not None:
if to_save[cc_string] is not None:
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
else:
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc)
else:
cc_class = db.cc_classes[c.id]
new_cc = cc_class(value=to_save[cc_string], book=book_id)
db.session.add(new_cc)
# handle cc data
edit_cc_data(book_id, book, to_save)
else:
if c.datatype == 'rating':
to_save[cc_string] = str(int(float(to_save[cc_string]) * 2))
if to_save[cc_string].strip() != cc_db_value:
if cc_db_value is not None:
# remove old cc_val
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0:
db.session.delete(del_cc)
cc_class = db.cc_classes[c.id]
new_cc = db.session.query(cc_class).filter(
cc_class.value == to_save[cc_string].strip()).first()
# if no cc val is found add it
if new_cc is None:
new_cc = cc_class(value=to_save[cc_string].strip())
db.session.add(new_cc)
db.session.flush()
new_cc = db.session.query(cc_class).filter(
cc_class.value == to_save[cc_string].strip()).first()
# add cc value to book
getattr(book, cc_string).append(new_cc)
else:
if cc_db_value is not None:
# remove old cc_val
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0:
db.session.delete(del_cc)
else:
input_tags = to_save[cc_string].split(',')
input_tags = list(map(lambda it: it.strip(), input_tags))
modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session,
'custom')
db.session.commit()
if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal()
if "detail_view" in to_save:
return redirect(url_for('show_book', book_id=book.id))
else:
for indx in range(0, len(book.languages)):
try:
book.languages[indx].language_name = LC.parse(book.languages[indx].lang_code).get_language_name(
get_locale())
except UnknownLocaleError:
book.languages[indx].language_name = _(
isoLanguages.get(part3=book.languages[indx].lang_code).name)
author_names = []
for authr in book.authors:
author_names.append(authr.name)
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
title=_(u"edit metadata"), page="editbook")
return render_edit_book(book_id)
else:
db.session.rollback()
flash(error, category="error")
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
title=_(u"edit metadata"), page="editbook")
return render_edit_book(book_id)
except Exception as e:
app.logger.exception(e)
db.session.rollback()

Loading…
Cancel
Save