mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-24 10:37:23 +00:00
Merge branch 'master' into Develop
# Conflicts: # cps/editbooks.py
This commit is contained in:
commit
48f4b12c0e
@ -36,10 +36,6 @@ from flask_principal import Principal
|
||||
from . import config_sql, logger, cache_buster, cli, ub, db
|
||||
from .reverseproxy import ReverseProxied
|
||||
from .server import WebServer
|
||||
try:
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
except ImportError:
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
|
||||
mimetypes.init()
|
||||
mimetypes.add_type('application/xhtml+xml', '.xhtml')
|
||||
@ -80,10 +76,7 @@ log = logger.create()
|
||||
from . import services
|
||||
|
||||
def create_app():
|
||||
try:
|
||||
app.wsgi_app = ReverseProxied(ProxyFix(app.wsgi_app, x_for=1, x_host=1))
|
||||
except (ValueError, TypeError):
|
||||
app.wsgi_app = ReverseProxied(ProxyFix(app.wsgi_app))
|
||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||
# For python2 convert path to unicode
|
||||
if sys.version_info < (3, 0):
|
||||
app.static_folder = app.static_folder.decode('utf-8')
|
||||
@ -95,7 +88,7 @@ def create_app():
|
||||
log.info('Starting Calibre Web...')
|
||||
Principal(app)
|
||||
lm.init_app(app)
|
||||
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
|
||||
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
||||
|
||||
web_server.init_app(app, config)
|
||||
db.setup_db(config)
|
||||
|
@ -22,7 +22,7 @@ import os
|
||||
import json
|
||||
import sys
|
||||
|
||||
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean
|
||||
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean, BLOB
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from . import constants, cli, logger, ub
|
||||
@ -31,6 +31,15 @@ from . import constants, cli, logger, ub
|
||||
log = logger.create()
|
||||
_Base = declarative_base()
|
||||
|
||||
class _Flask_Settings(_Base):
|
||||
__tablename__ = 'flask_settings'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
flask_session_key = Column(BLOB, default="")
|
||||
|
||||
def __init__(self, key):
|
||||
self.flask_session_key = key
|
||||
|
||||
|
||||
# Baseclass for representing settings in app.db with email server settings and Calibre database settings
|
||||
# (application settings)
|
||||
@ -304,7 +313,7 @@ def _migrate_table(session, orm_class):
|
||||
log.debug("%s: %s", column_name, err.args[0])
|
||||
if column.default is not None:
|
||||
if sys.version_info < (3, 0):
|
||||
if isinstance(column.default.arg,unicode):
|
||||
if isinstance(column.default.arg, unicode):
|
||||
column.default.arg = column.default.arg.encode('utf-8')
|
||||
if column.default is None:
|
||||
column_default = ""
|
||||
@ -340,6 +349,7 @@ def _migrate_database(session):
|
||||
# make sure the table is created, if it does not exist
|
||||
_Base.metadata.create_all(session.bind)
|
||||
_migrate_table(session, _Settings)
|
||||
_migrate_table(session, _Flask_Settings)
|
||||
|
||||
|
||||
def load_configuration(session):
|
||||
@ -357,3 +367,11 @@ def load_configuration(session):
|
||||
update({"denied_tags": conf.config_mature_content_tags}, synchronize_session=False)
|
||||
session.commit()
|
||||
return conf
|
||||
|
||||
def get_flask_session_key(session):
|
||||
flask_settings = session.query(_Flask_Settings).one_or_none()
|
||||
if flask_settings == None:
|
||||
flask_settings = _Flask_Settings(os.urandom(32))
|
||||
session.add(flask_settings)
|
||||
session.commit()
|
||||
return flask_settings.flask_session_key
|
||||
|
@ -127,7 +127,7 @@ def selected_roles(dictionary):
|
||||
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
|
||||
'series_id, languages')
|
||||
|
||||
STABLE_VERSION = {'version': '0.6.7 Beta'}
|
||||
STABLE_VERSION = {'version': '0.6.8 Beta'}
|
||||
|
||||
NIGHTLY_VERSION = {}
|
||||
NIGHTLY_VERSION[0] = '$Format:%H$'
|
||||
|
347
cps/editbooks.py
347
cps/editbooks.py
@ -30,6 +30,7 @@ from uuid import uuid4
|
||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
|
||||
from flask_babel import gettext as _
|
||||
from flask_login import current_user, login_required
|
||||
from sqlalchemy.exc import OperationalError
|
||||
|
||||
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
||||
from . import config, get_locale, db, ub, worker
|
||||
@ -444,10 +445,16 @@ def upload_single_file(request, book, book_id):
|
||||
if is_format:
|
||||
log.warning('Book format %s already existing', file_ext.upper())
|
||||
else:
|
||||
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
||||
db.session.add(db_format)
|
||||
db.session.commit()
|
||||
db.update_title_sort(config)
|
||||
try:
|
||||
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
||||
db.session.add(db_format)
|
||||
db.session.commit()
|
||||
db.update_title_sort(config)
|
||||
except OperationalError as e:
|
||||
db.session.rollback()
|
||||
log.error('Database error: %s', e)
|
||||
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
||||
return redirect(url_for('web.show_book', book_id=book.id))
|
||||
|
||||
# Queue uploader info
|
||||
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
|
||||
@ -455,7 +462,8 @@ def upload_single_file(request, book, book_id):
|
||||
"<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
|
||||
|
||||
return uploader.process(
|
||||
saved_filename, *os.path.splitext(requested_file.filename))
|
||||
saved_filename, *os.path.splitext(requested_file.filename),
|
||||
rarExcecutable=config.config_rarfile_location)
|
||||
|
||||
|
||||
def upload_cover(request, book):
|
||||
@ -653,181 +661,188 @@ def upload():
|
||||
abort(404)
|
||||
if request.method == 'POST' and 'btn-upload' in request.files:
|
||||
for requested_file in request.files.getlist("btn-upload"):
|
||||
# create the function for sorting...
|
||||
db.update_title_sort(config)
|
||||
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
||||
|
||||
# check if file extension is correct
|
||||
if '.' in requested_file.filename:
|
||||
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||
if file_ext not in constants.EXTENSIONS_UPLOAD:
|
||||
flash(
|
||||
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
||||
ext=file_ext), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
else:
|
||||
flash(_('File to be uploaded must have an extension'), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
|
||||
# extract metadata from file
|
||||
try:
|
||||
meta = uploader.upload(requested_file, config.config_rarfile_location)
|
||||
except (IOError, OSError):
|
||||
log.error("File %s could not saved to temp dir", requested_file.filename)
|
||||
flash(_(u"File %(filename)s could not saved to temp dir",
|
||||
filename= requested_file.filename), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
title = meta.title
|
||||
authr = meta.author
|
||||
tags = meta.tags
|
||||
series = meta.series
|
||||
series_index = meta.series_id
|
||||
# create the function for sorting...
|
||||
db.update_title_sort(config)
|
||||
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
||||
|
||||
if title != _(u'Unknown') and authr != _(u'Unknown'):
|
||||
entry = helper.check_exists_book(authr, title)
|
||||
if entry:
|
||||
log.info("Uploaded book probably exists in library")
|
||||
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
|
||||
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
|
||||
# check if file extension is correct
|
||||
if '.' in requested_file.filename:
|
||||
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||
if file_ext not in constants.EXTENSIONS_UPLOAD:
|
||||
flash(
|
||||
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
||||
ext=file_ext), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
else:
|
||||
flash(_('File to be uploaded must have an extension'), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
|
||||
# handle authors
|
||||
is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first()
|
||||
if is_author:
|
||||
db_author = is_author
|
||||
authr= is_author.name
|
||||
else:
|
||||
db_author = db.Authors(authr, helper.get_sorted_author(authr), "")
|
||||
db.session.add(db_author)
|
||||
|
||||
title_dir = helper.get_valid_filename(title)
|
||||
author_dir = helper.get_valid_filename(authr)
|
||||
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
|
||||
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
|
||||
|
||||
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
||||
if not os.path.exists(filepath):
|
||||
# extract metadata from file
|
||||
try:
|
||||
os.makedirs(filepath)
|
||||
except OSError:
|
||||
log.error("Failed to create path %s (Permission denied)", filepath)
|
||||
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
||||
meta = uploader.upload(requested_file, config.config_rarfile_location)
|
||||
except (IOError, OSError):
|
||||
log.error("File %s could not saved to temp dir", requested_file.filename)
|
||||
flash(_(u"File %(filename)s could not saved to temp dir",
|
||||
filename= requested_file.filename), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
try:
|
||||
copyfile(meta.file_path, saved_filename)
|
||||
except OSError:
|
||||
log.error("Failed to store file %s (Permission denied)", saved_filename)
|
||||
flash(_(u"Failed to store file %(file)s (Permission denied).", file=saved_filename), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
try:
|
||||
os.unlink(meta.file_path)
|
||||
except OSError:
|
||||
log.error("Failed to delete file %(file)s (Permission denied)", meta.file_path)
|
||||
flash(_(u"Failed to delete file %(file)s (Permission denied).", file= meta.file_path),
|
||||
category="warning")
|
||||
title = meta.title
|
||||
authr = meta.author
|
||||
tags = meta.tags
|
||||
series = meta.series
|
||||
series_index = meta.series_id
|
||||
|
||||
if meta.cover is None:
|
||||
has_cover = 0
|
||||
copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
|
||||
os.path.join(filepath, "cover.jpg"))
|
||||
else:
|
||||
has_cover = 1
|
||||
if title != _(u'Unknown') and authr != _(u'Unknown'):
|
||||
entry = helper.check_exists_book(authr, title)
|
||||
if entry:
|
||||
log.info("Uploaded book probably exists in library")
|
||||
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
|
||||
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
|
||||
|
||||
# handle series
|
||||
db_series = None
|
||||
is_series = db.session.query(db.Series).filter(db.Series.name == series).first()
|
||||
if is_series:
|
||||
db_series = is_series
|
||||
elif series != '':
|
||||
db_series = db.Series(series, "")
|
||||
db.session.add(db_series)
|
||||
|
||||
# add language actually one value in list
|
||||
input_language = meta.languages
|
||||
db_language = None
|
||||
if input_language != "":
|
||||
input_language = isoLanguages.get(name=input_language).part3
|
||||
hasLanguage = db.session.query(db.Languages).filter(db.Languages.lang_code == input_language).first()
|
||||
if hasLanguage:
|
||||
db_language = hasLanguage
|
||||
# handle authors
|
||||
is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first()
|
||||
if is_author:
|
||||
db_author = is_author
|
||||
authr= is_author.name
|
||||
else:
|
||||
db_language = db.Languages(input_language)
|
||||
db.session.add(db_language)
|
||||
db_author = db.Authors(authr, helper.get_sorted_author(authr), "")
|
||||
db.session.add(db_author)
|
||||
|
||||
# If the language of the file is excluded from the users view, it's not imported, to allow the user to view
|
||||
# the book it's language is set to the filter language
|
||||
if db_language != current_user.filter_language() and current_user.filter_language() != "all":
|
||||
db_language = db.session.query(db.Languages).\
|
||||
filter(db.Languages.lang_code == current_user.filter_language()).first()
|
||||
title_dir = helper.get_valid_filename(title)
|
||||
author_dir = helper.get_valid_filename(authr)
|
||||
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
|
||||
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
|
||||
|
||||
# combine path and normalize path from windows systems
|
||||
path = os.path.join(author_dir, title_dir).replace('\\', '/')
|
||||
# Calibre adds books with utc as timezone
|
||||
db_book = db.Books(title, "", db_author.sort, datetime.utcnow(), datetime(101, 1, 1),
|
||||
series_index, datetime.utcnow(), path, has_cover, db_author, [], db_language)
|
||||
db_book.authors.append(db_author)
|
||||
if db_series:
|
||||
db_book.series.append(db_series)
|
||||
if db_language is not None:
|
||||
db_book.languages.append(db_language)
|
||||
file_size = os.path.getsize(saved_filename)
|
||||
db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, title_dir)
|
||||
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
||||
if not os.path.exists(filepath):
|
||||
try:
|
||||
os.makedirs(filepath)
|
||||
except OSError:
|
||||
log.error("Failed to create path %s (Permission denied)", filepath)
|
||||
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
try:
|
||||
copyfile(meta.file_path, saved_filename)
|
||||
os.unlink(meta.file_path)
|
||||
except OSError as e:
|
||||
log.error("Failed to move file %s: %s", saved_filename, e)
|
||||
flash(_(u"Failed to Move File %(file)s: %(error)s", file=saved_filename, error=e), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
|
||||
# handle tags
|
||||
input_tags = tags.split(',')
|
||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
||||
if input_tags[0] !="":
|
||||
modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags')
|
||||
|
||||
# flush content, get db_book.id available
|
||||
db_book.data.append(db_data)
|
||||
db.session.add(db_book)
|
||||
db.session.flush()
|
||||
|
||||
# add comment
|
||||
book_id = db_book.id
|
||||
upload_comment = Markup(meta.description).unescape()
|
||||
if upload_comment != "":
|
||||
db.session.add(db.Comments(upload_comment, book_id))
|
||||
|
||||
# save data to database, reread data
|
||||
db.session.commit()
|
||||
db.update_title_sort(config)
|
||||
# Reread book. It's important not to filter the result, as it could have language which hide it from
|
||||
# current users view (tags are not stored/extracted from metadata and could also be limited)
|
||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||
# upload book to gdrive if nesseccary and add "(bookid)" to folder name
|
||||
if config.config_use_google_drive:
|
||||
gdriveutils.updateGdriveCalibreFromLocal()
|
||||
error = helper.update_dir_stucture(book.id, config.config_calibre_dir)
|
||||
|
||||
# move cover to final directory, including book id
|
||||
if has_cover:
|
||||
move(meta.cover, os.path.join(filepath+ ' ({})'.format(book_id), "cover.jpg"))
|
||||
db.session.commit()
|
||||
if config.config_use_google_drive:
|
||||
gdriveutils.updateGdriveCalibreFromLocal()
|
||||
if error:
|
||||
flash(error, category="error")
|
||||
uploadText=_(u"File %(file)s uploaded", file=book.title)
|
||||
worker.add_upload(current_user.nickname,
|
||||
"<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
|
||||
|
||||
# create data for displaying display Full language name instead of iso639.part3language
|
||||
if db_language is not None:
|
||||
book.languages[0].language_name = _(meta.languages)
|
||||
author_names = []
|
||||
for author in db_book.authors:
|
||||
author_names.append(author.name)
|
||||
if len(request.files.getlist("btn-upload")) < 2:
|
||||
if current_user.role_edit() or current_user.role_admin():
|
||||
resp = {"location": url_for('editbook.edit_book', book_id=db_book.id)}
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
if meta.cover is None:
|
||||
has_cover = 0
|
||||
copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
|
||||
os.path.join(filepath, "cover.jpg"))
|
||||
else:
|
||||
resp = {"location": url_for('web.show_book', book_id=db_book.id)}
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
has_cover = 1
|
||||
|
||||
# handle series
|
||||
db_series = None
|
||||
is_series = db.session.query(db.Series).filter(db.Series.name == series).first()
|
||||
if is_series:
|
||||
db_series = is_series
|
||||
elif series != '':
|
||||
db_series = db.Series(series, "")
|
||||
db.session.add(db_series)
|
||||
|
||||
# add language actually one value in list
|
||||
input_language = meta.languages
|
||||
db_language = None
|
||||
if input_language != "":
|
||||
input_language = isoLanguages.get(name=input_language).part3
|
||||
hasLanguage = db.session.query(db.Languages).filter(db.Languages.lang_code == input_language).first()
|
||||
if hasLanguage:
|
||||
db_language = hasLanguage
|
||||
else:
|
||||
db_language = db.Languages(input_language)
|
||||
db.session.add(db_language)
|
||||
|
||||
# If the language of the file is excluded from the users view, it's not imported, to allow the user to view
|
||||
# the book it's language is set to the filter language
|
||||
if db_language != current_user.filter_language() and current_user.filter_language() != "all":
|
||||
db_language = db.session.query(db.Languages).\
|
||||
filter(db.Languages.lang_code == current_user.filter_language()).first()
|
||||
|
||||
# combine path and normalize path from windows systems
|
||||
path = os.path.join(author_dir, title_dir).replace('\\', '/')
|
||||
# Calibre adds books with utc as timezone
|
||||
db_book = db.Books(title, "", db_author.sort, datetime.utcnow(), datetime(101, 1, 1),
|
||||
series_index, datetime.utcnow(), path, has_cover, db_author, [], db_language)
|
||||
db_book.authors.append(db_author)
|
||||
if db_series:
|
||||
db_book.series.append(db_series)
|
||||
if db_language is not None:
|
||||
db_book.languages.append(db_language)
|
||||
file_size = os.path.getsize(saved_filename)
|
||||
db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, title_dir)
|
||||
|
||||
# handle tags
|
||||
input_tags = tags.split(',')
|
||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
||||
if input_tags[0] !="":
|
||||
modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags')
|
||||
|
||||
# flush content, get db_book.id available
|
||||
db_book.data.append(db_data)
|
||||
db.session.add(db_book)
|
||||
db.session.flush()
|
||||
|
||||
# add comment
|
||||
book_id = db_book.id
|
||||
upload_comment = Markup(meta.description).unescape()
|
||||
if upload_comment != "":
|
||||
db.session.add(db.Comments(upload_comment, book_id))
|
||||
|
||||
# save data to database, reread data
|
||||
db.session.commit()
|
||||
db.update_title_sort(config)
|
||||
# Reread book. It's important not to filter the result, as it could have language which hide it from
|
||||
# current users view (tags are not stored/extracted from metadata and could also be limited)
|
||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||
# upload book to gdrive if nesseccary and add "(bookid)" to folder name
|
||||
if config.config_use_google_drive:
|
||||
gdriveutils.updateGdriveCalibreFromLocal()
|
||||
error = helper.update_dir_stucture(book.id, config.config_calibre_dir)
|
||||
|
||||
# move cover to final directory, including book id
|
||||
if has_cover:
|
||||
try:
|
||||
new_coverpath = os.path.join(filepath+ ' ({})'.format(book_id), "cover.jpg")
|
||||
copyfile(meta.cover, new_coverpath)
|
||||
os.unlink(meta.cover)
|
||||
except OSError as e:
|
||||
log.error("Failed to move cover file %s: %s", new_coverpath, e)
|
||||
flash(_(u"Failed to Move Cover File %(file)s: %(error)s", file=new_coverpath,
|
||||
error=e),
|
||||
category="error")
|
||||
db.session.commit()
|
||||
if config.config_use_google_drive:
|
||||
gdriveutils.updateGdriveCalibreFromLocal()
|
||||
if error:
|
||||
flash(error, category="error")
|
||||
uploadText=_(u"File %(file)s uploaded", file=book.title)
|
||||
worker.add_upload(current_user.nickname,
|
||||
"<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
|
||||
|
||||
# create data for displaying display Full language name instead of iso639.part3language
|
||||
if db_language is not None:
|
||||
book.languages[0].language_name = _(meta.languages)
|
||||
author_names = []
|
||||
for author in db_book.authors:
|
||||
author_names.append(author.name)
|
||||
if len(request.files.getlist("btn-upload")) < 2:
|
||||
if current_user.role_edit() or current_user.role_admin():
|
||||
resp = {"location": url_for('editbook.edit_book', book_id=db_book.id)}
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
else:
|
||||
resp = {"location": url_for('web.show_book', book_id=db_book.id)}
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
except OperationalError as e:
|
||||
db.session.rollback()
|
||||
log.error("Database error: %s", e)
|
||||
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
|
||||
|
||||
@editbook.route("/admin/book/convert/<int:book_id>", methods=['POST'])
|
||||
@login_required_if_no_ano
|
||||
@edit_required
|
||||
|
@ -291,6 +291,7 @@ def delete_book_file(book, calibrepath, book_format=None):
|
||||
for file in os.listdir(path):
|
||||
if file.upper().endswith("."+book_format):
|
||||
os.remove(os.path.join(path, file))
|
||||
return True, None
|
||||
else:
|
||||
if os.path.isdir(path):
|
||||
if len(next(os.walk(path))[1]):
|
||||
|
@ -90,15 +90,15 @@
|
||||
<label for="config_port">{{_('Server Port')}}</label>
|
||||
<input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if config.config_port != None %}{{ config.config_port }}{% endif %}" autocomplete="off" required>
|
||||
</div>
|
||||
<label for="config_certfile">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<label for="config_certfile" class="sr-only">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<input type="text" class="form-control" id="config_certfile" name="config_certfile" value="{% if config.config_certfile != None %}{{ config.config_certfile }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="certfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<label for="config_calibre_dir" >{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<label for="config_calibre_dir" class="sr-only">{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<input type="text" class="form-control" id="config_keyfile" name="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="keyfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
@ -349,23 +349,23 @@
|
||||
<label for="config_calibre">{{_('Calibre E-Book Converter Settings')}}</label>
|
||||
<input type="text" class="form-control" id="config_calibre" name="config_calibre" value="{% if config.config_calibre != None %}{{ config.config_calibre }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<label for="config_converterpath" class="sr-only">{{_('Path to Calibre E-Book Converter')}}</label>
|
||||
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if config.config_converterpath != None %}{{ config.config_converterpath }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<label for="config_kepubifypath">{{_('Path to Kepubify E-Book Converter')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<label for="config_kepubifypath" class="sr-only">{{_('Path to Kepubify E-Book Converter')}}</label>
|
||||
<input type="text" class="form-control" id="config_kepubifypath" name="config_kepubifypath" value="{% if config.config_kepubifypath != None %}{{ config.config_kepubifypath }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="kepubify_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
{% if feature_support['rar'] %}
|
||||
<label for="config_rarfile_location">{{_('Location of Unrar binary')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<label for="config_rarfile_location" class="sr-only">{{_('Location of Unrar binary')}}</label>
|
||||
<input type="text" class="form-control" id="config_rarfile_location" name="config_rarfile_location" value="{% if config.config_rarfile_location != None %}{{ config.config_rarfile_location }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="unrar_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
|
@ -6,8 +6,8 @@
|
||||
{% block body %}
|
||||
<div class="discover">
|
||||
<h2>{{title}}</h2>
|
||||
<form role="form" method="POST" autocomplete="off">
|
||||
<div class="panel-group col-md-10 col-lg-6">
|
||||
<form role="form" method="POST" autocomplete="off" class="col-md-10 col-lg-6">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
|
@ -35,12 +35,12 @@
|
||||
<label for="mail_from">{{_('From E-mail')}}</label>
|
||||
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
|
||||
</div>
|
||||
<label for="mail_size">{{_('Attachment Size Limit')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<label for="mail_size" class="sr-only">{{_('Attachment Size Limit')}}</label>
|
||||
<input type="number" min="1" max="600" class="form-control" name="attachment_size" id="mail_size" value="{% if config.mail_size != None %}{{ config.mail_size }}{% endif %}">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="certfile_path" class="btn btn-default" disabled>MB</button>
|
||||
</span>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="certfile_path" class="btn btn-default" disabled>MB</button>
|
||||
</span>
|
||||
</div>
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button>
|
||||
|
Binary file not shown.
@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Calibre-Web\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n"
|
||||
"POT-Creation-Date: 2020-05-01 17:15+0200\n"
|
||||
"POT-Creation-Date: 2020-05-04 20:19+0200\n"
|
||||
"PO-Revision-Date: 2017-04-04 15:09+0200\n"
|
||||
"Last-Translator: ElQuimm <quimm@webtaste.com>\n"
|
||||
"Language: it\n"
|
||||
@ -172,7 +172,7 @@ msgstr "Configurazione del server e-mail aggiornata"
|
||||
|
||||
#: cps/admin.py:821
|
||||
msgid "User not found"
|
||||
msgstr ""
|
||||
msgstr "Utente non trovato"
|
||||
|
||||
#: cps/admin.py:842
|
||||
#, python-format
|
||||
@ -185,7 +185,7 @@ msgstr "Non rimarrebbe nessun utente amministratore, non posso eliminare l'utent
|
||||
|
||||
#: cps/admin.py:851
|
||||
msgid "No admin user remaining, can't remove admin role"
|
||||
msgstr ""
|
||||
msgstr "Non rimarrebbe nessun utente amministratore, non posso eliminare il ruolo di amministratore"
|
||||
|
||||
#: cps/admin.py:887 cps/web.py:1515
|
||||
msgid "Found an existing account for this e-mail address."
|
||||
@ -285,11 +285,11 @@ msgstr "non configurato"
|
||||
|
||||
#: cps/editbooks.py:239
|
||||
msgid "Book Format Successfully Deleted"
|
||||
msgstr ""
|
||||
msgstr "Il formato del libro è stato eliminato con successo"
|
||||
|
||||
#: cps/editbooks.py:242
|
||||
msgid "Book Successfully Deleted"
|
||||
msgstr ""
|
||||
msgstr "Il libro é stato eliminato con successo"
|
||||
|
||||
#: cps/editbooks.py:253 cps/editbooks.py:489
|
||||
msgid "Error opening eBook. File does not exist or file is not accessible"
|
||||
@ -321,12 +321,12 @@ msgstr "Impossibile creare la cartella %(path)s (autorizzazione negata)."
|
||||
#: cps/editbooks.py:434
|
||||
#, python-format
|
||||
msgid "Failed to store file %(file)s."
|
||||
msgstr "Il salvataggio del file %(file)s è fallito."
|
||||
msgstr "Il salvataggio del file %(file)s non è riuscito."
|
||||
|
||||
#: cps/editbooks.py:451
|
||||
#, python-format
|
||||
msgid "File format %(ext)s added to %(book)s"
|
||||
msgstr "Ho aggiunto l'estensione %(ext)s al libro %(book)s"
|
||||
msgstr "Ho aggiunto il formato %(ext)s al libro %(book)s"
|
||||
|
||||
#: cps/editbooks.py:606
|
||||
msgid "Metadata successfully updated"
|
||||
@ -362,7 +362,7 @@ msgstr "Il file %(file)s è stato caricato"
|
||||
|
||||
#: cps/editbooks.py:833
|
||||
msgid "Source or destination format for conversion missing"
|
||||
msgstr "Il formato sorgente o quello di destinazione, necessari alla conversione, mancano"
|
||||
msgstr "Mancano o il formato sorgente o quello di destinazione, necessari alla conversione"
|
||||
|
||||
#: cps/editbooks.py:841
|
||||
#, python-format
|
||||
@ -446,17 +446,17 @@ msgstr "Il file richiesto non può essere letto. I permessi sono corretti?"
|
||||
#: cps/helper.py:299
|
||||
#, python-format
|
||||
msgid "Deleting book %(id)s failed, path has subfolders: %(path)s"
|
||||
msgstr ""
|
||||
msgstr "L'eliminazione del libro %(id)s non è riuscita, poiché il percorso ha delle sottocartelle: %(path)s"
|
||||
|
||||
#: cps/helper.py:309
|
||||
#, python-format
|
||||
msgid "Deleting book %(id)s failed: %(message)s"
|
||||
msgstr ""
|
||||
msgstr "L'eliminazione del libro %(id)s non è riuscita: %(message)s"
|
||||
|
||||
#: cps/helper.py:319
|
||||
#, python-format
|
||||
msgid "Deleting book %(id)s failed, book path not valid: %(path)s"
|
||||
msgstr ""
|
||||
msgstr "L'eliminazione del libro %(id)s non è riuscita, poiché il percorso non è valido: %(path)s"
|
||||
|
||||
#: cps/helper.py:354
|
||||
#, python-format
|
||||
@ -489,7 +489,7 @@ msgstr "Errore nel creare la cartella per la copertina"
|
||||
|
||||
#: cps/helper.py:555
|
||||
msgid "Cover-file is not a valid image file, or could not be stored"
|
||||
msgstr ""
|
||||
msgstr "Il file della copertina non è in un formato immagine valido o non può essere salvato"
|
||||
|
||||
#: cps/helper.py:566
|
||||
msgid "Only jpg/jpeg/png/webp files are supported as coverfile"
|
||||
@ -501,11 +501,11 @@ msgstr "Solamente i file nei formati jpg/jpeg sono supportati per le copertine"
|
||||
|
||||
#: cps/helper.py:622
|
||||
msgid "Unrar binary file not found"
|
||||
msgstr ""
|
||||
msgstr "Non ho trovato il file binario di UnRar"
|
||||
|
||||
#: cps/helper.py:635
|
||||
msgid "Error excecuting UnRar"
|
||||
msgstr ""
|
||||
msgstr "Errore nell'eseguire UnRar"
|
||||
|
||||
#: cps/helper.py:691
|
||||
msgid "Waiting"
|
||||
@ -558,19 +558,19 @@ msgstr "Registra con %(provider)s"
|
||||
|
||||
#: cps/oauth_bb.py:154
|
||||
msgid "Failed to log in with GitHub."
|
||||
msgstr "Accesso con GitHub non riuscito."
|
||||
msgstr "Accesso con GitHub non è riuscito."
|
||||
|
||||
#: cps/oauth_bb.py:159
|
||||
msgid "Failed to fetch user info from GitHub."
|
||||
msgstr "Fallito il recupero delle informazioni dell'utente da GitHub."
|
||||
msgstr "Il recupero delle informazioni dell'utente da GitHub non è riuscito."
|
||||
|
||||
#: cps/oauth_bb.py:170
|
||||
msgid "Failed to log in with Google."
|
||||
msgstr "Fallito l'accesso con Google."
|
||||
msgstr "L'accesso con Google non è riuscito."
|
||||
|
||||
#: cps/oauth_bb.py:175
|
||||
msgid "Failed to fetch user info from Google."
|
||||
msgstr "Fallito il recupero delle informazioni dell'utente da Google."
|
||||
msgstr "Il recupero delle informazioni dell'utente da Google non è riuscito."
|
||||
|
||||
#: cps/oauth_bb.py:225 cps/web.py:1291 cps/web.py:1431
|
||||
#, python-format
|
||||
@ -584,7 +584,7 @@ msgstr "Collegamento a %(oauth)s avvenuto con successo"
|
||||
|
||||
#: cps/oauth_bb.py:241
|
||||
msgid "Login failed, No User Linked With OAuth Account"
|
||||
msgstr "Accesso fallito, non c'è un utente collegato all'account OAuth"
|
||||
msgstr "Accesso non riuscito, non c'è un utente collegato all'account OAuth"
|
||||
|
||||
#: cps/oauth_bb.py:283
|
||||
#, python-format
|
||||
@ -594,7 +594,7 @@ msgstr "Scollegamento da %(oauth)s avvenuto con successo"
|
||||
#: cps/oauth_bb.py:287
|
||||
#, python-format
|
||||
msgid "Unlink to %(oauth)s Failed"
|
||||
msgstr "Scollegamento da %(oauth)s fallito"
|
||||
msgstr "Scollegamento da %(oauth)s non riuscito"
|
||||
|
||||
#: cps/oauth_bb.py:290
|
||||
#, python-format
|
||||
@ -818,11 +818,11 @@ msgstr "Mostra la selezione del formato dei file"
|
||||
|
||||
#: cps/ub.py:107 cps/web.py:1150
|
||||
msgid "Archived Books"
|
||||
msgstr ""
|
||||
msgstr "Libri archiviati"
|
||||
|
||||
#: cps/ub.py:109
|
||||
msgid "Show archived books"
|
||||
msgstr ""
|
||||
msgstr "Mostra l'opzione per la selezione dei libri archiviati"
|
||||
|
||||
#: cps/updater.py:294 cps/updater.py:305 cps/updater.py:406 cps/updater.py:420
|
||||
msgid "Unexpected data while reading update information"
|
||||
@ -1360,23 +1360,23 @@ msgstr "Descrizione"
|
||||
|
||||
#: cps/templates/book_edit.html:66
|
||||
msgid "Identifiers"
|
||||
msgstr ""
|
||||
msgstr "Identificatori"
|
||||
|
||||
#: cps/templates/book_edit.html:70 cps/templates/book_edit.html:308
|
||||
msgid "Identifier Type"
|
||||
msgstr ""
|
||||
msgstr "Tipo di identificatore"
|
||||
|
||||
#: cps/templates/book_edit.html:71 cps/templates/book_edit.html:309
|
||||
msgid "Identifier Value"
|
||||
msgstr ""
|
||||
msgstr "Valore dell'identificatore"
|
||||
|
||||
#: cps/templates/book_edit.html:72 cps/templates/book_edit.html:310
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
msgstr "Rimuovi"
|
||||
|
||||
#: cps/templates/book_edit.html:76
|
||||
msgid "Add Identifier"
|
||||
msgstr ""
|
||||
msgstr "Aggiungi un identificatore"
|
||||
|
||||
#: cps/templates/book_edit.html:80 cps/templates/search_form.html:33
|
||||
msgid "Tags"
|
||||
@ -1453,11 +1453,11 @@ msgstr "e dal disco rigido"
|
||||
|
||||
#: cps/templates/book_edit.html:209
|
||||
msgid "Important Kobo Note: deleted books will remain on any paired Kobo device."
|
||||
msgstr ""
|
||||
msgstr "Oservazione importante riguardo Kobo: i libri eliminati, rimarranno in ogni lettore Kobo accoppiato."
|
||||
|
||||
#: cps/templates/book_edit.html:210
|
||||
msgid "Books must first be archived and the device synced before a book can safely be deleted."
|
||||
msgstr ""
|
||||
msgstr "Prima di poter elimnare in sicurezza un libro, prima occorre che il libro venga archiviato e che l'apparecchio venga sincronizzato."
|
||||
|
||||
#: cps/templates/book_edit.html:232
|
||||
msgid "Keyword"
|
||||
@ -1775,7 +1775,7 @@ msgstr "Percorso del convertitore"
|
||||
|
||||
#: cps/templates/config_edit.html:349
|
||||
msgid "Location of Unrar binary"
|
||||
msgstr "Percorso di UnRar"
|
||||
msgstr "Percorso del file binario di UnRar"
|
||||
|
||||
#: cps/templates/config_edit.html:368 cps/templates/layout.html:84
|
||||
#: cps/templates/login.html:4 cps/templates/login.html:20
|
||||
@ -1912,15 +1912,15 @@ msgstr "da leggere"
|
||||
|
||||
#: cps/templates/detail.html:208
|
||||
msgid "Restore from archive"
|
||||
msgstr ""
|
||||
msgstr "Ripristina dall'archivio"
|
||||
|
||||
#: cps/templates/detail.html:208
|
||||
msgid "Add to archive"
|
||||
msgstr ""
|
||||
msgstr "Aggiungi all'archivio"
|
||||
|
||||
#: cps/templates/detail.html:209
|
||||
msgid "Archived"
|
||||
msgstr ""
|
||||
msgstr "Archiviato"
|
||||
|
||||
#: cps/templates/detail.html:219
|
||||
msgid "Description:"
|
||||
@ -2252,7 +2252,7 @@ msgstr "Scuro"
|
||||
|
||||
#: cps/templates/readcbr.html:121
|
||||
msgid "Scale"
|
||||
msgstr "Adatta"
|
||||
msgstr "Scala"
|
||||
|
||||
#: cps/templates/readcbr.html:124
|
||||
msgid "Best"
|
||||
@ -2396,7 +2396,7 @@ msgstr "Cambia ordine"
|
||||
|
||||
#: cps/templates/shelf.html:67
|
||||
msgid "Are you sure you want to delete this shelf?"
|
||||
msgstr "Vuoi davvero eliminare lo scaffale?"
|
||||
msgstr "Vuoi davvero eliminare questo scaffale?"
|
||||
|
||||
#: cps/templates/shelf.html:70
|
||||
msgid "Shelf will be deleted for all users"
|
||||
|
21
cps/web.py
21
cps/web.py
@ -37,9 +37,9 @@ from flask import Blueprint
|
||||
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
|
||||
from flask_babel import gettext as _
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from sqlalchemy.exc import IntegrityError, InvalidRequestError
|
||||
from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError
|
||||
from sqlalchemy.sql.expression import text, func, true, false, not_, and_, or_
|
||||
from werkzeug.exceptions import default_exceptions
|
||||
from werkzeug.exceptions import default_exceptions, InternalServerError
|
||||
try:
|
||||
from werkzeug.exceptions import FailedDependency
|
||||
except ImportError:
|
||||
@ -119,9 +119,16 @@ for ex in default_exceptions:
|
||||
if feature_support['ldap']:
|
||||
# Only way of catching the LDAPException upon logging in with LDAP server down
|
||||
@app.errorhandler(services.ldap.LDAPException)
|
||||
def handle_exception(e):
|
||||
log.debug('LDAP server not accessible while trying to login to opds feed')
|
||||
return error_http(FailedDependency())
|
||||
def handle_LDAP_exception(e):
|
||||
log.debug('LDAP server not accssible while trying to login to opds feed %s', e)
|
||||
return error_http(e)
|
||||
|
||||
# @app.errorhandler(InvalidRequestError)
|
||||
#@app.errorhandler(OperationalError)
|
||||
#def handle_db_exception(e):
|
||||
# db.session.rollback()
|
||||
# log.error('Database request error: %s',e)
|
||||
# return internal_error(InternalServerError(e))
|
||||
|
||||
|
||||
web = Blueprint('web', __name__)
|
||||
@ -435,6 +442,10 @@ def toggle_read(book_id):
|
||||
db.session.commit()
|
||||
except KeyError:
|
||||
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
|
||||
except OperationalError as e:
|
||||
db.session.rollback()
|
||||
log.error(u"Read status could not set: %e", e)
|
||||
|
||||
return ""
|
||||
|
||||
@web.route("/ajax/togglearchived/<int:book_id>", methods=['POST'])
|
||||
|
66
test/Calibre-Web TestSummary.html
Normal file → Executable file
66
test/Calibre-Web TestSummary.html
Normal file → Executable file
@ -36,17 +36,17 @@
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;">
|
||||
<p class='text-justify attribute'><strong>Start Time: </strong>2020-05-01 13:35:57</p>
|
||||
<p class='text-justify attribute'><strong>Start Time: </strong>2020-05-05 19:02:03</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
||||
<p class='text-justify attribute'><strong>Stop Time: </strong>2020-05-01 14:32:26</p>
|
||||
<p class='text-justify attribute'><strong>Stop Time: </strong>2020-05-05 19:58:37</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
||||
<p class='text-justify attribute'><strong>Duration: </strong>47:49 min</p>
|
||||
<p class='text-justify attribute'><strong>Duration: </strong>47:42 min</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1829,8 +1829,8 @@ AssertionError: False is not true : logfile config value is not empty after rese
|
||||
<tr class="result['header']['style']">
|
||||
<td>test_updater.test_updater</td>
|
||||
<td class="text-center">7</td>
|
||||
<td class="text-center">6</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">5</td>
|
||||
<td class="text-center">1</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">1</td>
|
||||
<td class="text-center">
|
||||
@ -1867,11 +1867,33 @@ AssertionError: False is not true : logfile config value is not empty after rese
|
||||
|
||||
|
||||
|
||||
<tr id='pt18.4' class='hiddenRow bg-success'>
|
||||
<tr id='ft18.4' class='none bg-danger'>
|
||||
<td>
|
||||
<div class='testcase'>test_check_update_stable_versions</div>
|
||||
</td>
|
||||
<td colspan='6' align='center'>PASS</td>
|
||||
<td colspan='6'>
|
||||
<div class="text-center">
|
||||
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft18.4')">FAIL</a>
|
||||
</div>
|
||||
<!--css div popup start-->
|
||||
<div id='div_ft18.4' class="popup_window test_output" style="display:none;">
|
||||
<div class='close_button pull-right'>
|
||||
<button type="button" class="close" aria-label="Close" onfocus='this.blur();'
|
||||
onclick="document.getElementById('div_ft18.4').style.display='none'"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<div class="text-left pull-left">
|
||||
<pre class="text-left">Traceback (most recent call last):
|
||||
File "/home/matthias/Entwicklung/calibre-web-test/test/test_updater.py", line 150, in test_check_update_stable_versions
|
||||
self.check_updater('latest version installed', "alert-warning")
|
||||
File "/home/matthias/Entwicklung/calibre-web-test/test/test_updater.py", line 60, in check_updater
|
||||
self.assertTrue(self.check_element_on_page((By.CLASS_NAME, className)))
|
||||
AssertionError: False is not true</pre>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<!--css div popup end-->
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@ -1924,8 +1946,8 @@ AssertionError: False is not true : logfile config value is not empty after rese
|
||||
<tr class="result['header']['style']">
|
||||
<td>test_user_template.test_user_template</td>
|
||||
<td class="text-center">19</td>
|
||||
<td class="text-center">18</td>
|
||||
<td class="text-center">1</td>
|
||||
<td class="text-center">19</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">
|
||||
@ -2088,31 +2110,11 @@ AssertionError: False is not true : logfile config value is not empty after rese
|
||||
|
||||
|
||||
|
||||
<tr id='ft19.18' class='none bg-danger'>
|
||||
<tr id='pt19.18' class='hiddenRow bg-success'>
|
||||
<td>
|
||||
<div class='testcase'>test_series_user_template</div>
|
||||
</td>
|
||||
<td colspan='6'>
|
||||
<div class="text-center">
|
||||
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft19.18')">FAIL</a>
|
||||
</div>
|
||||
<!--css div popup start-->
|
||||
<div id='div_ft19.18' class="popup_window test_output" style="display:none;">
|
||||
<div class='close_button pull-right'>
|
||||
<button type="button" class="close" aria-label="Close" onfocus='this.blur();'
|
||||
onclick="document.getElementById('div_ft19.18').style.display='none'"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<div class="text-left pull-left">
|
||||
<pre class="text-left">Traceback (most recent call last):
|
||||
File "/home/matthias/Entwicklung/calibre-web-test/test/test_user_template.py", line 193, in test_series_user_template
|
||||
self.assertTrue(self.check_element_on_page((By.ID, "nav_hot")))
|
||||
AssertionError: False is not true</pre>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<!--css div popup end-->
|
||||
</td>
|
||||
<td colspan='6' align='center'>PASS</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@ -2574,7 +2576,7 @@ AssertionError: False is not true</pre>
|
||||
|
||||
<tr>
|
||||
<th>SQLAlchemy-Utils</th>
|
||||
<td>0.36.4</td>
|
||||
<td>0.36.5</td>
|
||||
<td>test_OAuth_login</td>
|
||||
</tr>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user