Merge branch 'Develop'
# Conflicts: # test/Calibre-Web TestSummary_Linux.html
This commit is contained in:
@@ -25,7 +25,8 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask, request
|
||||||
|
from flask.sessions import SecureCookieSessionInterface
|
||||||
from .MyLoginManager import MyLoginManager
|
from .MyLoginManager import MyLoginManager
|
||||||
from flask_principal import Principal
|
from flask_principal import Principal
|
||||||
|
|
||||||
@@ -114,8 +115,14 @@ if limiter_present:
|
|||||||
else:
|
else:
|
||||||
limiter = None
|
limiter = None
|
||||||
|
|
||||||
|
class ScriptNameSessionInterface(SecureCookieSessionInterface):
|
||||||
|
def get_cookie_path(self, app):
|
||||||
|
# Called once per response, after request context exists
|
||||||
|
return app.wsgi_app.script_name.rstrip("/") or "/"
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
app.session_interface = ScriptNameSessionInterface()
|
||||||
if csrf:
|
if csrf:
|
||||||
csrf.init_app(app)
|
csrf.init_app(app)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from flask import abort
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask import has_app_context
|
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask import session
|
from flask import session
|
||||||
@@ -469,7 +468,7 @@ class LoginManager:
|
|||||||
config = current_app.config
|
config = current_app.config
|
||||||
cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
|
cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
|
||||||
domain = config.get("REMEMBER_COOKIE_DOMAIN")
|
domain = config.get("REMEMBER_COOKIE_DOMAIN")
|
||||||
path = config.get("REMEMBER_COOKIE_PATH", "/")
|
path = config.get("REMEMBER_COOKIE_PATH", current_app.wsgi_app.script_name)
|
||||||
|
|
||||||
secure = config.get("REMEMBER_COOKIE_SECURE", COOKIE_SECURE)
|
secure = config.get("REMEMBER_COOKIE_SECURE", COOKIE_SECURE)
|
||||||
httponly = config.get("REMEMBER_COOKIE_HTTPONLY", COOKIE_HTTPONLY)
|
httponly = config.get("REMEMBER_COOKIE_HTTPONLY", COOKIE_HTTPONLY)
|
||||||
@@ -520,36 +519,5 @@ class LoginManager:
|
|||||||
config = current_app.config
|
config = current_app.config
|
||||||
cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
|
cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
|
||||||
domain = config.get("REMEMBER_COOKIE_DOMAIN")
|
domain = config.get("REMEMBER_COOKIE_DOMAIN")
|
||||||
path = config.get("REMEMBER_COOKIE_PATH", "/")
|
path = config.get("REMEMBER_COOKIE_PATH", current_app.wsgi_app.script_name)
|
||||||
response.delete_cookie(cookie_name, domain=domain, path=path)
|
response.delete_cookie(cookie_name, domain=domain, path=path)
|
||||||
|
|
||||||
@property
|
|
||||||
def _login_disabled(self):
|
|
||||||
"""Legacy property, use app.config['LOGIN_DISABLED'] instead."""
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'_login_disabled' is deprecated and will be removed in"
|
|
||||||
" Flask-Login 0.7. Use 'LOGIN_DISABLED' in 'app.config'"
|
|
||||||
" instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
if has_app_context():
|
|
||||||
return current_app.config.get("LOGIN_DISABLED", False)
|
|
||||||
return False
|
|
||||||
|
|
||||||
@_login_disabled.setter
|
|
||||||
def _login_disabled(self, newvalue):
|
|
||||||
"""Legacy property setter, use app.config['LOGIN_DISABLED'] instead."""
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'_login_disabled' is deprecated and will be removed in"
|
|
||||||
" Flask-Login 0.7. Use 'LOGIN_DISABLED' in 'app.config'"
|
|
||||||
" instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
current_app.config["LOGIN_DISABLED"] = newvalue
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def get_sidebar_config(kwargs=None):
|
|||||||
if current_user.role_admin():
|
if current_user.role_admin():
|
||||||
sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.download_list',
|
sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.download_list',
|
||||||
"id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not current_user.is_anonymous),
|
"id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not current_user.is_anonymous),
|
||||||
"page": "download", "show_text": _('Show Downloaded Books'),
|
"page": "download", "show_text": _('Show Downloaded Books'), "no_param":True,
|
||||||
"config_show": content})
|
"config_show": content})
|
||||||
else:
|
else:
|
||||||
sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list',
|
sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list',
|
||||||
@@ -69,27 +69,27 @@ def get_sidebar_config(kwargs=None):
|
|||||||
"visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover",
|
"visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover",
|
||||||
"show_text": _('Show Random Books'), "config_show": True})
|
"show_text": _('Show Random Books'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
|
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
|
||||||
"visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category",
|
"visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category", "no_param":True,
|
||||||
"show_text": _('Show Category Section'), "config_show": True})
|
"show_text": _('Show Category Section'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie",
|
sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie",
|
||||||
"visibility": constants.SIDEBAR_SERIES, 'public': True, "page": "series",
|
"visibility": constants.SIDEBAR_SERIES, 'public': True, "page": "series", "no_param":True,
|
||||||
"show_text": _('Show Series Section'), "config_show": True})
|
"show_text": _('Show Series Section'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-user", "text": _('Authors'), "link": 'web.author_list', "id": "author",
|
sidebar.append({"glyph": "glyphicon-user", "text": _('Authors'), "link": 'web.author_list', "id": "author",
|
||||||
"visibility": constants.SIDEBAR_AUTHOR, 'public': True, "page": "author",
|
"visibility": constants.SIDEBAR_AUTHOR, 'public': True, "page": "author", "no_param":True,
|
||||||
"show_text": _('Show Author Section'), "config_show": True})
|
"show_text": _('Show Author Section'), "config_show": True})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-text-size", "text": _('Publishers'), "link": 'web.publisher_list', "id": "publisher",
|
{"glyph": "glyphicon-text-size", "text": _('Publishers'), "link": 'web.publisher_list', "id": "publisher",
|
||||||
"visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher",
|
"visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher", "no_param":True,
|
||||||
"show_text": _('Show Publisher Section'), "config_show":True})
|
"show_text": _('Show Publisher Section'), "config_show":True})
|
||||||
sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang",
|
sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang",
|
||||||
"visibility": constants.SIDEBAR_LANGUAGE, 'public': (current_user.filter_language() == 'all'),
|
"visibility": constants.SIDEBAR_LANGUAGE, 'public': (current_user.filter_language() == 'all'),
|
||||||
"page": "language",
|
"page": "language", "no_param":True,
|
||||||
"show_text": _('Show Language Section'), "config_show": True})
|
"show_text": _('Show Language Section'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate",
|
sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate",
|
||||||
"visibility": constants.SIDEBAR_RATING, 'public': True,
|
"visibility": constants.SIDEBAR_RATING, 'public': True, "no_param":True,
|
||||||
"page": "rating", "show_text": _('Show Ratings Section'), "config_show": True})
|
"page": "rating", "show_text": _('Show Ratings Section'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format",
|
sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format",
|
||||||
"visibility": constants.SIDEBAR_FORMAT, 'public': True,
|
"visibility": constants.SIDEBAR_FORMAT, 'public': True, "no_param":True,
|
||||||
"page": "format", "show_text": _('Show File Formats Section'), "config_show": True})
|
"page": "format", "show_text": _('Show File Formats Section'), "config_show": True})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-folder-open", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
|
{"glyph": "glyphicon-folder-open", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
|
||||||
@@ -98,8 +98,8 @@ def get_sidebar_config(kwargs=None):
|
|||||||
if not simple:
|
if not simple:
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_table', "id": "list",
|
{"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_table', "id": "list",
|
||||||
"visibility": constants.SIDEBAR_LIST, 'public': (not current_user.is_anonymous), "page": "list",
|
"visibility": constants.SIDEBAR_LIST, 'public': (not current_user.is_anonymous),
|
||||||
"show_text": _('Show Books List'), "config_show": content})
|
"show_text": _('Show Books List'), "config_show": content, "no_param":True})
|
||||||
g.shelves_access = ub.session.query(ub.Shelf).filter(
|
g.shelves_access = ub.session.query(ub.Shelf).filter(
|
||||||
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
|
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
|
||||||
|
|
||||||
|
|||||||
@@ -61,11 +61,13 @@ class ReverseProxied(object):
|
|||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
self.proxied = False
|
self.proxied = False
|
||||||
|
self.script_name = "/"
|
||||||
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
||||||
if script_name:
|
if script_name:
|
||||||
self.proxied = True
|
self.proxied = True
|
||||||
environ['SCRIPT_NAME'] = script_name
|
environ['SCRIPT_NAME'] = script_name
|
||||||
path_info = environ.get('PATH_INFO', '')
|
path_info = environ.get('PATH_INFO', '')
|
||||||
|
self.script_name = script_name
|
||||||
if path_info and path_info.startswith(script_name):
|
if path_info and path_info.startswith(script_name):
|
||||||
environ['PATH_INFO'] = path_info[len(script_name):]
|
environ['PATH_INFO'] = path_info[len(script_name):]
|
||||||
|
|
||||||
|
|||||||
50
cps/shelf.py
50
cps/shelf.py
@@ -102,6 +102,52 @@ def add_to_shelf(shelf_id, book_id):
|
|||||||
return "", 204
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
@shelf.route("/shelf/massremove/<int:shelf_id>", methods=["POST"])
|
||||||
|
@user_login_required
|
||||||
|
def search_from_shelf(shelf_id):
|
||||||
|
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
|
||||||
|
if shelf is None:
|
||||||
|
log.error("Invalid shelf specified: {}".format(shelf_id))
|
||||||
|
flash(_("Invalid shelf specified"), category="error")
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
|
||||||
|
if not check_shelf_edit_permissions(shelf):
|
||||||
|
log.warning("You are not allowed to remove a book from the shelf".format(shelf.name))
|
||||||
|
flash(_("You are not allowed to remove a book from the shelf"), category="error")
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
|
||||||
|
if current_user.id in ub.searched_ids and ub.searched_ids[current_user.id]:
|
||||||
|
books_from_shelf = list()
|
||||||
|
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).all()
|
||||||
|
if books_in_shelf:
|
||||||
|
book_ids = [book_id.book_id for book_id in books_in_shelf]
|
||||||
|
for searchid in ub.searched_ids[current_user.id]:
|
||||||
|
if searchid in book_ids:
|
||||||
|
books_from_shelf.append(searchid)
|
||||||
|
else:
|
||||||
|
log.error("No Books are part of {}".format(shelf.name))
|
||||||
|
flash(_("No Books are part of the shelf: %(name)s", name=shelf.name), category="error")
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
|
||||||
|
# maxOrder = ub.session.query(func.max(ub.BookShelf.order)).filter(ub.BookShelf.shelf == shelf_id).first()[0] or 0
|
||||||
|
|
||||||
|
for book in books_from_shelf:
|
||||||
|
ub.session.delete(ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).filter(
|
||||||
|
ub.BookShelf.book_id == book).first())
|
||||||
|
shelf.last_modified = datetime.now(timezone.utc)
|
||||||
|
try:
|
||||||
|
ub.session.commit()
|
||||||
|
flash(_("Books have been removed from shelf: %(sname)s", sname=shelf.name), category="success")
|
||||||
|
except (OperationalError, InvalidRequestError) as e:
|
||||||
|
ub.session.rollback()
|
||||||
|
log.error_or_exception("Settings Database error: {}".format(e))
|
||||||
|
flash(_("Oops! Database Error: %(error)s.", error=e.orig), category="error")
|
||||||
|
else:
|
||||||
|
log.error("Could not remove books from shelf: {}".format(shelf.name))
|
||||||
|
flash(_("Could not remove books from shelf: %(sname)s", sname=shelf.name), category="error")
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
|
||||||
|
|
||||||
@shelf.route("/shelf/massadd/<int:shelf_id>", methods=["POST"])
|
@shelf.route("/shelf/massadd/<int:shelf_id>", methods=["POST"])
|
||||||
@user_login_required
|
@user_login_required
|
||||||
def search_to_shelf(shelf_id):
|
def search_to_shelf(shelf_id):
|
||||||
@@ -120,9 +166,7 @@ def search_to_shelf(shelf_id):
|
|||||||
books_for_shelf = list()
|
books_for_shelf = list()
|
||||||
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).all()
|
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).all()
|
||||||
if books_in_shelf:
|
if books_in_shelf:
|
||||||
book_ids = list()
|
book_ids = [book_id.book_id for book_id in books_in_shelf]
|
||||||
for book_id in books_in_shelf:
|
|
||||||
book_ids.append(book_id.book_id)
|
|
||||||
for searchid in ub.searched_ids[current_user.id]:
|
for searchid in ub.searched_ids[current_user.id]:
|
||||||
if searchid not in book_ids:
|
if searchid not in book_ids:
|
||||||
books_for_shelf.append(searchid)
|
books_for_shelf.append(searchid)
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
<li class="nav-head hidden-xs">{{_('Browse')}}</li>
|
<li class="nav-head hidden-xs">{{_('Browse')}}</li>
|
||||||
{% for element in sidebar %}
|
{% for element in sidebar %}
|
||||||
{% if current_user.check_visibility(element['visibility']) and element['public'] %}
|
{% if current_user.check_visibility(element['visibility']) and element['public'] %}
|
||||||
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'], data=element['page'], sort_param='stored')}}"><span class="glyphicon {{element['glyph']}}"></span> {{_(element['text'])}}</a></li>
|
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{% if element['no_param'] %}{{url_for(element['link'], data=element['page'])}}{%else%}{{url_for(element['link'], data=element['page'], sort_param='stored')}}{% endif %}"><span class="glyphicon {{element['glyph']}}"></span> {{_(element['text'])}}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if current_user.is_authenticated or g.allow_anonymous %}
|
{% if current_user.is_authenticated or g.allow_anonymous %}
|
||||||
|
|||||||
@@ -24,6 +24,20 @@
|
|||||||
{%endfor%}
|
{%endfor%}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-group" role="group" aria-label="Remove from shelves">
|
||||||
|
<button id="remove-from-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="glyphicon glyphicon-list"></span> {{_('Remove from shelf')}}
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul id="remove-from-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
|
||||||
|
{% for shelf in g.shelves_access %}
|
||||||
|
{% if not shelf.is_public or current_user.role_edit_shelfs() %}
|
||||||
|
<li><a class="postAction" role="button" data-action="{{ url_for('shelf.search_from_shelf', shelf_id=shelf.id) }}"> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{%endfor%}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user