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:
commit
48f4b12c0e
@ -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)
|
||||||
|
@ -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)
|
||||||
@ -304,7 +313,7 @@ def _migrate_table(session, orm_class):
|
|||||||
log.debug("%s: %s", column_name, err.args[0])
|
log.debug("%s: %s", column_name, err.args[0])
|
||||||
if column.default is not None:
|
if column.default is not None:
|
||||||
if sys.version_info < (3, 0):
|
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')
|
column.default.arg = column.default.arg.encode('utf-8')
|
||||||
if column.default is None:
|
if column.default is None:
|
||||||
column_default = ""
|
column_default = ""
|
||||||
@ -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
|
||||||
|
@ -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$'
|
||||||
|
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 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:
|
||||||
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
try:
|
||||||
db.session.add(db_format)
|
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
||||||
db.session.commit()
|
db.session.add(db_format)
|
||||||
db.update_title_sort(config)
|
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
|
# 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,181 +661,188 @@ 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"):
|
||||||
# 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:
|
try:
|
||||||
meta = uploader.upload(requested_file, config.config_rarfile_location)
|
# create the function for sorting...
|
||||||
except (IOError, OSError):
|
db.update_title_sort(config)
|
||||||
log.error("File %s could not saved to temp dir", requested_file.filename)
|
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
||||||
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
|
|
||||||
|
|
||||||
if title != _(u'Unknown') and authr != _(u'Unknown'):
|
# check if file extension is correct
|
||||||
entry = helper.check_exists_book(authr, title)
|
if '.' in requested_file.filename:
|
||||||
if entry:
|
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||||
log.info("Uploaded book probably exists in library")
|
if file_ext not in constants.EXTENSIONS_UPLOAD:
|
||||||
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
|
flash(
|
||||||
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
|
_("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
|
# extract metadata from file
|
||||||
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):
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(filepath)
|
meta = uploader.upload(requested_file, config.config_rarfile_location)
|
||||||
except OSError:
|
except (IOError, OSError):
|
||||||
log.error("Failed to create path %s (Permission denied)", filepath)
|
log.error("File %s could not saved to temp dir", requested_file.filename)
|
||||||
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
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')
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
try:
|
title = meta.title
|
||||||
copyfile(meta.file_path, saved_filename)
|
authr = meta.author
|
||||||
except OSError:
|
tags = meta.tags
|
||||||
log.error("Failed to store file %s (Permission denied)", saved_filename)
|
series = meta.series
|
||||||
flash(_(u"Failed to store file %(file)s (Permission denied).", file=saved_filename), category="error")
|
series_index = meta.series_id
|
||||||
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")
|
|
||||||
|
|
||||||
if meta.cover is None:
|
if title != _(u'Unknown') and authr != _(u'Unknown'):
|
||||||
has_cover = 0
|
entry = helper.check_exists_book(authr, title)
|
||||||
copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
|
if entry:
|
||||||
os.path.join(filepath, "cover.jpg"))
|
log.info("Uploaded book probably exists in library")
|
||||||
else:
|
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
|
||||||
has_cover = 1
|
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
|
||||||
|
|
||||||
# handle series
|
# handle authors
|
||||||
db_series = None
|
is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first()
|
||||||
is_series = db.session.query(db.Series).filter(db.Series.name == series).first()
|
if is_author:
|
||||||
if is_series:
|
db_author = is_author
|
||||||
db_series = is_series
|
authr= is_author.name
|
||||||
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:
|
else:
|
||||||
db_language = db.Languages(input_language)
|
db_author = db.Authors(authr, helper.get_sorted_author(authr), "")
|
||||||
db.session.add(db_language)
|
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
|
title_dir = helper.get_valid_filename(title)
|
||||||
# the book it's language is set to the filter language
|
author_dir = helper.get_valid_filename(authr)
|
||||||
if db_language != current_user.filter_language() and current_user.filter_language() != "all":
|
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
|
||||||
db_language = db.session.query(db.Languages).\
|
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
|
||||||
filter(db.Languages.lang_code == current_user.filter_language()).first()
|
|
||||||
|
|
||||||
# combine path and normalize path from windows systems
|
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
||||||
path = os.path.join(author_dir, title_dir).replace('\\', '/')
|
if not os.path.exists(filepath):
|
||||||
# Calibre adds books with utc as timezone
|
try:
|
||||||
db_book = db.Books(title, "", db_author.sort, datetime.utcnow(), datetime(101, 1, 1),
|
os.makedirs(filepath)
|
||||||
series_index, datetime.utcnow(), path, has_cover, db_author, [], db_language)
|
except OSError:
|
||||||
db_book.authors.append(db_author)
|
log.error("Failed to create path %s (Permission denied)", filepath)
|
||||||
if db_series:
|
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
||||||
db_book.series.append(db_series)
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
if db_language is not None:
|
try:
|
||||||
db_book.languages.append(db_language)
|
copyfile(meta.file_path, saved_filename)
|
||||||
file_size = os.path.getsize(saved_filename)
|
os.unlink(meta.file_path)
|
||||||
db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, title_dir)
|
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
|
if meta.cover is None:
|
||||||
input_tags = tags.split(',')
|
has_cover = 0
|
||||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
|
||||||
if input_tags[0] !="":
|
os.path.join(filepath, "cover.jpg"))
|
||||||
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')
|
|
||||||
else:
|
else:
|
||||||
resp = {"location": url_for('web.show_book', book_id=db_book.id)}
|
has_cover = 1
|
||||||
return Response(json.dumps(resp), mimetype='application/json')
|
|
||||||
|
# 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')
|
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
|
||||||
|
@ -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]):
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -35,12 +35,12 @@
|
|||||||
<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>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
|
<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>
|
<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 ""
|
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"
|
||||||
|
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 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
66
test/Calibre-Web TestSummary.html
Normal file → Executable 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">×</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">×</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>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user