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 . import config_sql, logger, cache_buster, cli, ub, db
from .reverseproxy import ReverseProxied from .reverseproxy import ReverseProxied
from .server import WebServer from .server import WebServer
try:
from werkzeug.middleware.proxy_fix import ProxyFix
except ImportError:
from werkzeug.contrib.fixers import ProxyFix
mimetypes.init() mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml') mimetypes.add_type('application/xhtml+xml', '.xhtml')
@ -80,10 +76,7 @@ log = logger.create()
from . import services from . import services
def create_app(): def create_app():
try: app.wsgi_app = ReverseProxied(app.wsgi_app)
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))
# For python2 convert path to unicode # For python2 convert path to unicode
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
app.static_folder = app.static_folder.decode('utf-8') app.static_folder = app.static_folder.decode('utf-8')
@ -95,7 +88,7 @@ def create_app():
log.info('Starting Calibre Web...') log.info('Starting Calibre Web...')
Principal(app) Principal(app)
lm.init_app(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) web_server.init_app(app, config)
db.setup_db(config) db.setup_db(config)

View File

@ -22,7 +22,7 @@ import os
import json import json
import sys 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 sqlalchemy.ext.declarative import declarative_base
from . import constants, cli, logger, ub from . import constants, cli, logger, ub
@ -31,6 +31,15 @@ from . import constants, cli, logger, ub
log = logger.create() log = logger.create()
_Base = declarative_base() _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 # Baseclass for representing settings in app.db with email server settings and Calibre database settings
# (application settings) # (application settings)
@ -340,6 +349,7 @@ def _migrate_database(session):
# make sure the table is created, if it does not exist # make sure the table is created, if it does not exist
_Base.metadata.create_all(session.bind) _Base.metadata.create_all(session.bind)
_migrate_table(session, _Settings) _migrate_table(session, _Settings)
_migrate_table(session, _Flask_Settings)
def load_configuration(session): def load_configuration(session):
@ -357,3 +367,11 @@ def load_configuration(session):
update({"denied_tags": conf.config_mature_content_tags}, synchronize_session=False) update({"denied_tags": conf.config_mature_content_tags}, synchronize_session=False)
session.commit() session.commit()
return conf 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, ' BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
'series_id, languages') 'series_id, languages')
STABLE_VERSION = {'version': '0.6.7 Beta'} STABLE_VERSION = {'version': '0.6.8 Beta'}
NIGHTLY_VERSION = {} NIGHTLY_VERSION = {}
NIGHTLY_VERSION[0] = '$Format:%H$' 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 import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import current_user, login_required from flask_login import current_user, login_required
from sqlalchemy.exc import OperationalError
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
from . import config, get_locale, db, ub, worker from . import config, get_locale, db, ub, worker
@ -444,10 +445,16 @@ def upload_single_file(request, book, book_id):
if is_format: if is_format:
log.warning('Book format %s already existing', file_ext.upper()) log.warning('Book format %s already existing', file_ext.upper())
else: else:
try:
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name) db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
db.session.add(db_format) db.session.add(db_format)
db.session.commit() db.session.commit()
db.update_title_sort(config) 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 # Queue uploader info
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title) 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>") "<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
return uploader.process( 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): def upload_cover(request, book):
@ -653,6 +661,7 @@ def upload():
abort(404) abort(404)
if request.method == 'POST' and 'btn-upload' in request.files: if request.method == 'POST' and 'btn-upload' in request.files:
for requested_file in request.files.getlist("btn-upload"): for requested_file in request.files.getlist("btn-upload"):
try:
# create the function for sorting... # create the function for sorting...
db.update_title_sort(config) db.update_title_sort(config)
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4())) 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') return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
try: try:
copyfile(meta.file_path, saved_filename) 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) os.unlink(meta.file_path)
except OSError: except OSError as e:
log.error("Failed to delete file %(file)s (Permission denied)", meta.file_path) log.error("Failed to move file %s: %s", saved_filename, e)
flash(_(u"Failed to delete file %(file)s (Permission denied).", file= meta.file_path), flash(_(u"Failed to Move File %(file)s: %(error)s", file=saved_filename, error=e), category="error")
category="warning") return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
if meta.cover is None: if meta.cover is None:
has_cover = 0 has_cover = 0
@ -802,7 +806,15 @@ def upload():
# move cover to final directory, including book id # move cover to final directory, including book id
if has_cover: 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() db.session.commit()
if config.config_use_google_drive: if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal() gdriveutils.updateGdriveCalibreFromLocal()
@ -825,9 +837,12 @@ def upload():
else: else:
resp = {"location": url_for('web.show_book', book_id=db_book.id)} resp = {"location": url_for('web.show_book', book_id=db_book.id)}
return Response(json.dumps(resp), mimetype='application/json') 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') return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
@editbook.route("/admin/book/convert/<int:book_id>", methods=['POST']) @editbook.route("/admin/book/convert/<int:book_id>", methods=['POST'])
@login_required_if_no_ano @login_required_if_no_ano
@edit_required @edit_required

View File

@ -291,6 +291,7 @@ def delete_book_file(book, calibrepath, book_format=None):
for file in os.listdir(path): for file in os.listdir(path):
if file.upper().endswith("."+book_format): if file.upper().endswith("."+book_format):
os.remove(os.path.join(path, file)) os.remove(os.path.join(path, file))
return True, None
else: else:
if os.path.isdir(path): if os.path.isdir(path):
if len(next(os.walk(path))[1]): if len(next(os.walk(path))[1]):

View File

@ -90,15 +90,15 @@
<label for="config_port">{{_('Server Port')}}</label> <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> <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> </div>
<label for="config_certfile">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
<div class="form-group input-group"> <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"> <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"> <span class="input-group-btn">
<button type="button" id="certfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button> <button type="button" id="certfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span> </span>
</div> </div>
<label for="config_calibre_dir" >{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
<div class="form-group input-group"> <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"> <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"> <span class="input-group-btn">
<button type="button" id="keyfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button> <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> <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"> <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> </div>
<label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label>
<div class="form-group input-group"> <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"> <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"> <span class="input-group-btn">
<button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button> <button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span> </span>
</div> </div>
<label for="config_kepubifypath">{{_('Path to Kepubify E-Book Converter')}}</label>
<div class="form-group input-group"> <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"> <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"> <span class="input-group-btn">
<button type="button" id="kepubify_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button> <button type="button" id="kepubify_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span> </span>
</div> </div>
{% if feature_support['rar'] %} {% if feature_support['rar'] %}
<label for="config_rarfile_location">{{_('Location of Unrar binary')}}</label>
<div class="form-group input-group"> <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"> <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"> <span class="input-group-btn">
<button type="button" id="unrar_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button> <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 %} {% block body %}
<div class="discover"> <div class="discover">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off"> <form role="form" method="POST" autocomplete="off" class="col-md-10 col-lg-6">
<div class="panel-group col-md-10 col-lg-6"> <div class="panel-group">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">

View File

@ -35,8 +35,8 @@
<label for="mail_from">{{_('From E-mail')}}</label> <label for="mail_from">{{_('From E-mail')}}</label>
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}"> <input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
</div> </div>
<label for="mail_size">{{_('Attachment Size Limit')}}</label>
<div class="form-group input-group"> <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 %}"> <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"> <span class="input-group-btn">
<button type="button" id="certfile_path" class="btn btn-default" disabled>MB</button> <button type="button" id="certfile_path" class="btn btn-default" disabled>MB</button>

View File

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/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" "PO-Revision-Date: 2017-04-04 15:09+0200\n"
"Last-Translator: ElQuimm <quimm@webtaste.com>\n" "Last-Translator: ElQuimm <quimm@webtaste.com>\n"
"Language: it\n" "Language: it\n"
@ -172,7 +172,7 @@ msgstr "Configurazione del server e-mail aggiornata"
#: cps/admin.py:821 #: cps/admin.py:821
msgid "User not found" msgid "User not found"
msgstr "" msgstr "Utente non trovato"
#: cps/admin.py:842 #: cps/admin.py:842
#, python-format #, python-format
@ -185,7 +185,7 @@ msgstr "Non rimarrebbe nessun utente amministratore, non posso eliminare l'utent
#: cps/admin.py:851 #: cps/admin.py:851
msgid "No admin user remaining, can't remove admin role" 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 #: cps/admin.py:887 cps/web.py:1515
msgid "Found an existing account for this e-mail address." msgid "Found an existing account for this e-mail address."
@ -285,11 +285,11 @@ msgstr "non configurato"
#: cps/editbooks.py:239 #: cps/editbooks.py:239
msgid "Book Format Successfully Deleted" msgid "Book Format Successfully Deleted"
msgstr "" msgstr "Il formato del libro è stato eliminato con successo"
#: cps/editbooks.py:242 #: cps/editbooks.py:242
msgid "Book Successfully Deleted" msgid "Book Successfully Deleted"
msgstr "" msgstr "Il libro é stato eliminato con successo"
#: cps/editbooks.py:253 cps/editbooks.py:489 #: cps/editbooks.py:253 cps/editbooks.py:489
msgid "Error opening eBook. File does not exist or file is not accessible" 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 #: cps/editbooks.py:434
#, python-format #, python-format
msgid "Failed to store file %(file)s." 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 #: cps/editbooks.py:451
#, python-format #, python-format
msgid "File format %(ext)s added to %(book)s" 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 #: cps/editbooks.py:606
msgid "Metadata successfully updated" msgid "Metadata successfully updated"
@ -362,7 +362,7 @@ msgstr "Il file %(file)s è stato caricato"
#: cps/editbooks.py:833 #: cps/editbooks.py:833
msgid "Source or destination format for conversion missing" 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 #: cps/editbooks.py:841
#, python-format #, python-format
@ -446,17 +446,17 @@ msgstr "Il file richiesto non può essere letto. I permessi sono corretti?"
#: cps/helper.py:299 #: cps/helper.py:299
#, python-format #, python-format
msgid "Deleting book %(id)s failed, path has subfolders: %(path)s" 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 #: cps/helper.py:309
#, python-format #, python-format
msgid "Deleting book %(id)s failed: %(message)s" msgid "Deleting book %(id)s failed: %(message)s"
msgstr "" msgstr "L'eliminazione del libro %(id)s non è riuscita: %(message)s"
#: cps/helper.py:319 #: cps/helper.py:319
#, python-format #, python-format
msgid "Deleting book %(id)s failed, book path not valid: %(path)s" 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 #: cps/helper.py:354
#, python-format #, python-format
@ -489,7 +489,7 @@ msgstr "Errore nel creare la cartella per la copertina"
#: cps/helper.py:555 #: cps/helper.py:555
msgid "Cover-file is not a valid image file, or could not be stored" 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 #: cps/helper.py:566
msgid "Only jpg/jpeg/png/webp files are supported as coverfile" 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 #: cps/helper.py:622
msgid "Unrar binary file not found" msgid "Unrar binary file not found"
msgstr "" msgstr "Non ho trovato il file binario di UnRar"
#: cps/helper.py:635 #: cps/helper.py:635
msgid "Error excecuting UnRar" msgid "Error excecuting UnRar"
msgstr "" msgstr "Errore nell'eseguire UnRar"
#: cps/helper.py:691 #: cps/helper.py:691
msgid "Waiting" msgid "Waiting"
@ -558,19 +558,19 @@ msgstr "Registra con %(provider)s"
#: cps/oauth_bb.py:154 #: cps/oauth_bb.py:154
msgid "Failed to log in with GitHub." msgid "Failed to log in with GitHub."
msgstr "Accesso con GitHub non riuscito." msgstr "Accesso con GitHub non è riuscito."
#: cps/oauth_bb.py:159 #: cps/oauth_bb.py:159
msgid "Failed to fetch user info from GitHub." 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 #: cps/oauth_bb.py:170
msgid "Failed to log in with Google." 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 #: cps/oauth_bb.py:175
msgid "Failed to fetch user info from Google." 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 #: cps/oauth_bb.py:225 cps/web.py:1291 cps/web.py:1431
#, python-format #, python-format
@ -584,7 +584,7 @@ msgstr "Collegamento a %(oauth)s avvenuto con successo"
#: cps/oauth_bb.py:241 #: cps/oauth_bb.py:241
msgid "Login failed, No User Linked With OAuth Account" 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 #: cps/oauth_bb.py:283
#, python-format #, python-format
@ -594,7 +594,7 @@ msgstr "Scollegamento da %(oauth)s avvenuto con successo"
#: cps/oauth_bb.py:287 #: cps/oauth_bb.py:287
#, python-format #, python-format
msgid "Unlink to %(oauth)s Failed" msgid "Unlink to %(oauth)s Failed"
msgstr "Scollegamento da %(oauth)s fallito" msgstr "Scollegamento da %(oauth)s non riuscito"
#: cps/oauth_bb.py:290 #: cps/oauth_bb.py:290
#, python-format #, python-format
@ -818,11 +818,11 @@ msgstr "Mostra la selezione del formato dei file"
#: cps/ub.py:107 cps/web.py:1150 #: cps/ub.py:107 cps/web.py:1150
msgid "Archived Books" msgid "Archived Books"
msgstr "" msgstr "Libri archiviati"
#: cps/ub.py:109 #: cps/ub.py:109
msgid "Show archived books" 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 #: cps/updater.py:294 cps/updater.py:305 cps/updater.py:406 cps/updater.py:420
msgid "Unexpected data while reading update information" msgid "Unexpected data while reading update information"
@ -1360,23 +1360,23 @@ msgstr "Descrizione"
#: cps/templates/book_edit.html:66 #: cps/templates/book_edit.html:66
msgid "Identifiers" msgid "Identifiers"
msgstr "" msgstr "Identificatori"
#: cps/templates/book_edit.html:70 cps/templates/book_edit.html:308 #: cps/templates/book_edit.html:70 cps/templates/book_edit.html:308
msgid "Identifier Type" msgid "Identifier Type"
msgstr "" msgstr "Tipo di identificatore"
#: cps/templates/book_edit.html:71 cps/templates/book_edit.html:309 #: cps/templates/book_edit.html:71 cps/templates/book_edit.html:309
msgid "Identifier Value" msgid "Identifier Value"
msgstr "" msgstr "Valore dell'identificatore"
#: cps/templates/book_edit.html:72 cps/templates/book_edit.html:310 #: cps/templates/book_edit.html:72 cps/templates/book_edit.html:310
msgid "Remove" msgid "Remove"
msgstr "" msgstr "Rimuovi"
#: cps/templates/book_edit.html:76 #: cps/templates/book_edit.html:76
msgid "Add Identifier" msgid "Add Identifier"
msgstr "" msgstr "Aggiungi un identificatore"
#: cps/templates/book_edit.html:80 cps/templates/search_form.html:33 #: cps/templates/book_edit.html:80 cps/templates/search_form.html:33
msgid "Tags" msgid "Tags"
@ -1453,11 +1453,11 @@ msgstr "e dal disco rigido"
#: cps/templates/book_edit.html:209 #: cps/templates/book_edit.html:209
msgid "Important Kobo Note: deleted books will remain on any paired Kobo device." 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 #: cps/templates/book_edit.html:210
msgid "Books must first be archived and the device synced before a book can safely be deleted." 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 #: cps/templates/book_edit.html:232
msgid "Keyword" msgid "Keyword"
@ -1775,7 +1775,7 @@ msgstr "Percorso del convertitore"
#: cps/templates/config_edit.html:349 #: cps/templates/config_edit.html:349
msgid "Location of Unrar binary" 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/config_edit.html:368 cps/templates/layout.html:84
#: cps/templates/login.html:4 cps/templates/login.html:20 #: cps/templates/login.html:4 cps/templates/login.html:20
@ -1912,15 +1912,15 @@ msgstr "da leggere"
#: cps/templates/detail.html:208 #: cps/templates/detail.html:208
msgid "Restore from archive" msgid "Restore from archive"
msgstr "" msgstr "Ripristina dall'archivio"
#: cps/templates/detail.html:208 #: cps/templates/detail.html:208
msgid "Add to archive" msgid "Add to archive"
msgstr "" msgstr "Aggiungi all'archivio"
#: cps/templates/detail.html:209 #: cps/templates/detail.html:209
msgid "Archived" msgid "Archived"
msgstr "" msgstr "Archiviato"
#: cps/templates/detail.html:219 #: cps/templates/detail.html:219
msgid "Description:" msgid "Description:"
@ -2252,7 +2252,7 @@ msgstr "Scuro"
#: cps/templates/readcbr.html:121 #: cps/templates/readcbr.html:121
msgid "Scale" msgid "Scale"
msgstr "Adatta" msgstr "Scala"
#: cps/templates/readcbr.html:124 #: cps/templates/readcbr.html:124
msgid "Best" msgid "Best"
@ -2396,7 +2396,7 @@ msgstr "Cambia ordine"
#: cps/templates/shelf.html:67 #: cps/templates/shelf.html:67
msgid "Are you sure you want to delete this shelf?" 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 #: cps/templates/shelf.html:70
msgid "Shelf will be deleted for all users" 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 import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import login_user, logout_user, login_required, current_user 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 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: try:
from werkzeug.exceptions import FailedDependency from werkzeug.exceptions import FailedDependency
except ImportError: except ImportError:
@ -119,9 +119,16 @@ for ex in default_exceptions:
if feature_support['ldap']: if feature_support['ldap']:
# Only way of catching the LDAPException upon logging in with LDAP server down # Only way of catching the LDAPException upon logging in with LDAP server down
@app.errorhandler(services.ldap.LDAPException) @app.errorhandler(services.ldap.LDAPException)
def handle_exception(e): def handle_LDAP_exception(e):
log.debug('LDAP server not accessible while trying to login to opds feed') log.debug('LDAP server not accssible while trying to login to opds feed %s', e)
return error_http(FailedDependency()) 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__) web = Blueprint('web', __name__)
@ -435,6 +442,10 @@ def toggle_read(book_id):
db.session.commit() db.session.commit()
except KeyError: except KeyError:
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column) 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 "" return ""
@web.route("/ajax/togglearchived/<int:book_id>", methods=['POST']) @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="col-xs-12 col-sm-6">
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;"> <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> </div>
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3"> <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> </div>
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3"> <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> </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']"> <tr class="result['header']['style']">
<td>test_updater.test_updater</td> <td>test_updater.test_updater</td>
<td class="text-center">7</td> <td class="text-center">7</td>
<td class="text-center">6</td> <td class="text-center">5</td>
<td class="text-center">0</td> <td class="text-center">1</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center">1</td> <td class="text-center">1</td>
<td class="text-center"> <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> <td>
<div class='testcase'>test_check_update_stable_versions</div> <div class='testcase'>test_check_update_stable_versions</div>
</td> </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> </tr>
@ -1924,8 +1946,8 @@ AssertionError: False is not true : logfile config value is not empty after rese
<tr class="result['header']['style']"> <tr class="result['header']['style']">
<td>test_user_template.test_user_template</td> <td>test_user_template.test_user_template</td>
<td class="text-center">19</td> <td class="text-center">19</td>
<td class="text-center">18</td> <td class="text-center">19</td>
<td class="text-center">1</td> <td class="text-center">0</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center"> <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> <td>
<div class='testcase'>test_series_user_template</div> <div class='testcase'>test_series_user_template</div>
</td> </td>
<td colspan='6'> <td colspan='6' align='center'>PASS</td>
<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>
</tr> </tr>
@ -2574,7 +2576,7 @@ AssertionError: False is not true</pre>
<tr> <tr>
<th>SQLAlchemy-Utils</th> <th>SQLAlchemy-Utils</th>
<td>0.36.4</td> <td>0.36.5</td>
<td>test_OAuth_login</td> <td>test_OAuth_login</td>
</tr> </tr>