1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-24 18:47:23 +00:00

Merge branch 'master' into Develop

# Conflicts:
#	cps/editbooks.py
This commit is contained in:
Ozzieisaacs 2020-05-06 18:47:33 +02:00
commit 48f4b12c0e
12 changed files with 301 additions and 261 deletions

View File

@ -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)

View File

@ -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

View File

@ -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$'

View File

@ -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:
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,6 +661,7 @@ def upload():
abort(404)
if request.method == 'POST' and 'btn-upload' in request.files:
for requested_file in request.files.getlist("btn-upload"):
try:
# create the function for sorting...
db.update_title_sort(config)
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
@ -714,16 +723,11 @@ def upload():
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")
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')
if meta.cover is None:
has_cover = 0
@ -802,7 +806,15 @@ def upload():
# move cover to final directory, including book id
if has_cover:
move(meta.cover, os.path.join(filepath+ ' ({})'.format(book_id), "cover.jpg"))
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()
@ -825,9 +837,12 @@ def upload():
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

View File

@ -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]):

View File

@ -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>

View File

@ -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">

View File

@ -35,8 +35,8 @@
<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>

View File

@ -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"

View File

@ -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
View 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">&times;</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">&times;</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>