This commit is contained in:
cbartondock 2021-04-14 09:30:05 -04:00
commit 2f3f13afb9
8 changed files with 530 additions and 133 deletions

View File

@ -245,7 +245,7 @@ def list_users():
off = int(request.args.get("offset") or 0) off = int(request.args.get("offset") or 0)
limit = int(request.args.get("limit") or 10) limit = int(request.args.get("limit") or 10)
search = request.args.get("search") search = request.args.get("search")
sort = request.args.get("sort", "state") sort = request.args.get("sort", "id")
order = request.args.get("order", "").lower() order = request.args.get("order", "").lower()
state = None state = None
if sort == "state": if sort == "state":
@ -254,7 +254,7 @@ def list_users():
if sort != "state" and order: if sort != "state" and order:
order = text(sort + " " + order) order = text(sort + " " + order)
elif not state: elif not state:
order = ub.User.name.desc() order = ub.User.id.asc()
all_user = ub.session.query(ub.User) all_user = ub.session.query(ub.User)
if not config.config_anonbrowse: if not config.config_anonbrowse:
@ -371,7 +371,7 @@ def edit_list_user(param):
'message':_(u"No admin user remaining, can't remove admin role", 'message':_(u"No admin user remaining, can't remove admin role",
nick=user.name)}), mimetype='application/json') nick=user.name)}), mimetype='application/json')
user.role &= ~int(vals['field_index']) user.role &= ~int(vals['field_index'])
elif param == 'sidebar_view': elif param.startswith('sidebar'):
if user.name == "Guest" and int(vals['field_index']) == constants.SIDEBAR_READ_AND_UNREAD: if user.name == "Guest" and int(vals['field_index']) == constants.SIDEBAR_READ_AND_UNREAD:
raise Exception(_("Guest can't have this view")) raise Exception(_("Guest can't have this view"))
if vals['value'] == 'true': if vals['value'] == 'true':

View File

@ -324,19 +324,19 @@ def delete_book(book_id, book_format, jsonResponse):
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper()) result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
if not result: if not result:
if jsonResponse: if jsonResponse:
return json.dumps({"location": url_for("editbook.edit_book"), return json.dumps([{"location": url_for("editbook.edit_book", book_id=book_id),
"type": "alert", "type": "danger",
"format": "", "format": "",
"error": error}), "message": error}])
else: else:
flash(error, category="error") flash(error, category="error")
return redirect(url_for('editbook.edit_book', book_id=book_id)) return redirect(url_for('editbook.edit_book', book_id=book_id))
if error: if error:
if jsonResponse: if jsonResponse:
warning = {"location": url_for("editbook.edit_book"), warning = {"location": url_for("editbook.edit_book", book_id=book_id),
"type": "warning", "type": "warning",
"format": "", "format": "",
"error": error} "message": error}
else: else:
flash(error, category="warning") flash(error, category="warning")
if not book_format: if not book_format:
@ -348,6 +348,15 @@ def delete_book(book_id, book_format, jsonResponse):
except Exception as ex: except Exception as ex:
log.debug_or_exception(ex) log.debug_or_exception(ex)
calibre_db.session.rollback() calibre_db.session.rollback()
if jsonResponse:
return json.dumps([{"location": url_for("editbook.edit_book", book_id=book_id),
"type": "danger",
"format": "",
"message": ex}])
else:
flash(str(ex), category="error")
return redirect(url_for('editbook.edit_book', book_id=book_id))
else: else:
# book not found # book not found
log.error('Book with id "%s" could not be deleted: not found', book_id) log.error('Book with id "%s" could not be deleted: not found', book_id)

View File

@ -30,6 +30,7 @@ from flask_babel import gettext as _
from flask_dance.consumer import oauth_authorized, oauth_error from flask_dance.consumer import oauth_authorized, oauth_error
from flask_dance.contrib.github import make_github_blueprint, github from flask_dance.contrib.github import make_github_blueprint, github
from flask_dance.contrib.google import make_google_blueprint, google from flask_dance.contrib.google import make_google_blueprint, google
from oauthlib.oauth2 import TokenExpiredError, InvalidGrantError
from flask_login import login_user, current_user, login_required from flask_login import login_user, current_user, login_required
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
@ -146,6 +147,7 @@ def bind_oauth_or_register(provider_id, provider_user_id, redirect_url, provider
ub.session.add(oauth_entry) ub.session.add(oauth_entry)
ub.session.commit() ub.session.commit()
flash(_(u"Link to %(oauth)s Succeeded", oauth=provider_name), category="success") flash(_(u"Link to %(oauth)s Succeeded", oauth=provider_name), category="success")
log.info("Link to {} Succeeded".format(provider_name))
return redirect(url_for('web.profile')) return redirect(url_for('web.profile'))
except Exception as ex: except Exception as ex:
log.debug_or_exception(ex) log.debug_or_exception(ex)
@ -194,6 +196,7 @@ def unlink_oauth(provider):
ub.session.commit() ub.session.commit()
logout_oauth_user() logout_oauth_user()
flash(_(u"Unlink to %(oauth)s Succeeded", oauth=oauth_check[provider]), category="success") flash(_(u"Unlink to %(oauth)s Succeeded", oauth=oauth_check[provider]), category="success")
log.info("Unlink to {} Succeeded".format(oauth_check[provider]))
except Exception as ex: except Exception as ex:
log.debug_or_exception(ex) log.debug_or_exception(ex)
ub.session.rollback() ub.session.rollback()
@ -257,11 +260,13 @@ if ub.oauth_support:
def github_logged_in(blueprint, token): def github_logged_in(blueprint, token):
if not token: if not token:
flash(_(u"Failed to log in with GitHub."), category="error") flash(_(u"Failed to log in with GitHub."), category="error")
log.error("Failed to log in with GitHub")
return False return False
resp = blueprint.session.get("/user") resp = blueprint.session.get("/user")
if not resp.ok: if not resp.ok:
flash(_(u"Failed to fetch user info from GitHub."), category="error") flash(_(u"Failed to fetch user info from GitHub."), category="error")
log.error("Failed to fetch user info from GitHub")
return False return False
github_info = resp.json() github_info = resp.json()
@ -273,11 +278,13 @@ if ub.oauth_support:
def google_logged_in(blueprint, token): def google_logged_in(blueprint, token):
if not token: if not token:
flash(_(u"Failed to log in with Google."), category="error") flash(_(u"Failed to log in with Google."), category="error")
log.error("Failed to log in with Google")
return False return False
resp = blueprint.session.get("/oauth2/v2/userinfo") resp = blueprint.session.get("/oauth2/v2/userinfo")
if not resp.ok: if not resp.ok:
flash(_(u"Failed to fetch user info from Google."), category="error") flash(_(u"Failed to fetch user info from Google."), category="error")
log.error("Failed to fetch user info from Google")
return False return False
google_info = resp.json() google_info = resp.json()
@ -318,11 +325,16 @@ if ub.oauth_support:
def github_login(): def github_login():
if not github.authorized: if not github.authorized:
return redirect(url_for('github.login')) return redirect(url_for('github.login'))
account_info = github.get('/user') try:
if account_info.ok: account_info = github.get('/user')
account_info_json = account_info.json() if account_info.ok:
return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github') account_info_json = account_info.json()
flash(_(u"GitHub Oauth error, please retry later."), category="error") return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github')
flash(_(u"GitHub Oauth error, please retry later."), category="error")
log.error("GitHub Oauth error, please retry later")
except (InvalidGrantError, TokenExpiredError) as e:
flash(_(u"GitHub Oauth error: {}").format(e), category="error")
log.error(e)
return redirect(url_for('web.login')) return redirect(url_for('web.login'))
@ -337,11 +349,16 @@ def github_login_unlink():
def google_login(): def google_login():
if not google.authorized: if not google.authorized:
return redirect(url_for("google.login")) return redirect(url_for("google.login"))
resp = google.get("/oauth2/v2/userinfo") try:
if resp.ok: resp = google.get("/oauth2/v2/userinfo")
account_info_json = resp.json() if resp.ok:
return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google') account_info_json = resp.json()
flash(_(u"Google Oauth error, please retry later."), category="error") return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google')
flash(_(u"Google Oauth error, please retry later."), category="error")
log.error("Google Oauth error, please retry later")
except (InvalidGrantError, TokenExpiredError) as e:
flash(_(u"Google Oauth error: {}").format(e), category="error")
log.error(e)
return redirect(url_for('web.login')) return redirect(url_for('web.login'))

View File

@ -1,6 +1,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% macro text_table_row(parameter, edit_text, show_text, validate) -%} {% macro text_table_row(parameter, edit_text, show_text, validate, sort) -%}
<th data-field="{{ parameter }}" id="{{ parameter }}" data-sortable="true" <th data-field="{{ parameter }}" id="{{ parameter }}"
{% if sort %}data-sortable="true" {% endif %}
data-visible = "{{visiblility.get(parameter)}}" data-visible = "{{visiblility.get(parameter)}}"
{% if g.user.role_edit() %} {% if g.user.role_edit() %}
data-editable-type="text" data-editable-type="text"
@ -43,16 +44,16 @@
<th data-field="state" data-checkbox="true" data-sortable="true"></th> <th data-field="state" data-checkbox="true" data-sortable="true"></th>
{% endif %} {% endif %}
<th data-field="id" id="id" data-visible="false" data-switchable="false"></th> <th data-field="id" id="id" data-visible="false" data-switchable="false"></th>
{{ text_table_row('title', _('Enter Title'),_('Title'), true) }} {{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }}
{{ text_table_row('sort', _('Enter Title Sort'),_('Title Sort'), false) }} {{ text_table_row('sort', _('Enter Title Sort'),_('Title Sort'), false, true) }}
{{ text_table_row('author_sort', _('Enter Author Sort'),_('Author Sort'), false) }} {{ text_table_row('author_sort', _('Enter Author Sort'),_('Author Sort'), false, true) }}
{{ text_table_row('authors', _('Enter Authors'),_('Authors'), true) }} {{ text_table_row('authors', _('Enter Authors'),_('Authors'), true) }}
{{ text_table_row('tags', _('Enter Categories'),_('Categories'), false) }} {{ text_table_row('tags', _('Enter Categories'),_('Categories'), false, false) }}
{{ text_table_row('series', _('Enter Series'),_('Series'), false) }} {{ text_table_row('series', _('Enter Series'),_('Series'), false, false) }}
<th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('editbook.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter title')}}"{% endif %}>{{_('Series Index')}}</th> <th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('editbook.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter title')}}"{% endif %}>{{_('Series Index')}}</th>
{{ text_table_row('languages', _('Enter Languages'),_('Languages'), false) }} {{ text_table_row('languages', _('Enter Languages'),_('Languages'), false, false) }}
<!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th--> <!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th-->
{{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false) }} {{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false, false) }}
{% if g.user.role_delete_books() and g.user.role_edit()%} {% if g.user.role_delete_books() and g.user.role_edit()%}
<th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th> <th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th>
{% endif %} {% endif %}

View File

@ -92,7 +92,7 @@
{% for message in get_flashed_messages(with_categories=True) %} {% for message in get_flashed_messages(with_categories=True) %}
{%if message[0] == "error" %} {%if message[0] == "error" %}
<div class="row-fluid text-center" style="margin-top: -20px;"> <div class="row-fluid text-center" style="margin-top: -20px;">
<div id="flash_alert" class="alert alert-danger">{{ message[1] }}</div> <div id="flash_danger" class="alert alert-danger">{{ message[1] }}</div>
</div> </div>
{%endif%} {%endif%}
{%if message[0] == "info" %} {%if message[0] == "info" %}

View File

@ -26,6 +26,7 @@ from datetime import datetime
import json import json
import mimetypes import mimetypes
import chardet # dependency of requests import chardet # dependency of requests
import copy
from babel.dates import format_date from babel.dates import format_date
from babel import Locale as LC from babel import Locale as LC
@ -756,13 +757,12 @@ def list_books():
off = int(request.args.get("offset") or 0) off = int(request.args.get("offset") or 0)
limit = int(request.args.get("limit") or config.config_books_per_page) limit = int(request.args.get("limit") or config.config_books_per_page)
search = request.args.get("search") search = request.args.get("search")
sort = request.args.get("sort", "state") sort = request.args.get("sort", "id")
order = request.args.get("order", "").lower() order = request.args.get("order", "").lower()
state = None state = None
if sort == "state": if sort == "state":
state = json.loads(request.args.get("state", "[]")) state = json.loads(request.args.get("state", "[]"))
if sort != "state" and order: if sort != "state" and order:
order = [text(sort + " " + order)] order = [text(sort + " " + order)]
elif not state: elif not state:
@ -831,9 +831,12 @@ def author_list():
charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \ charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \ .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all() .group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
for entry in entries: # If not creating a copy, readonly databases can not display authornames with "|" in it as changing the name
# starts a change session
autor_copy = copy.deepcopy(entries)
for entry in autor_copy:
entry.Authors.name = entry.Authors.name.replace('|', ',') entry.Authors.name = entry.Authors.name.replace('|', ',')
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, return render_title_template('list.html', entries=autor_copy, folder='web.books_list', charlist=charlist,
title=u"Authors", page="authorlist", data='author', order=order_no) title=u"Authors", page="authorlist", data='author', order=order_no)
else: else:
abort(404) abort(404)

View File

@ -26,7 +26,7 @@ python-ldap>=3.0.0,<3.4.0
Flask-SimpleLDAP>=1.4.0,<1.5.0 Flask-SimpleLDAP>=1.4.0,<1.5.0
#oauth #oauth
Flask-Dance>=1.4.0,<3.1.0 Flask-Dance>=1.4.0,<4.1.0
SQLAlchemy-Utils>=0.33.5,<0.38.0 SQLAlchemy-Utils>=0.33.5,<0.38.0
# extracting metadata # extracting metadata

File diff suppressed because one or more lines are too long