Merge branch 'master' of https://github.com/xlivevil/calibre-web
This commit is contained in:
commit
fb97e39d9f
|
@ -41,6 +41,6 @@ Open a new GitHub pull request with the patch. Ensure the PR description clearly
|
|||
|
||||
In case your code enhances features of Calibre-Web: Create your pull request for the development branch if your enhancement consists of more than some lines of code in a local section of Calibre-Webs code. This makes it easier to test it and check all implication before it's made public.
|
||||
|
||||
Please check if your code runs on Python 2.7 (still necessary in 2020) and mainly on python 3. If possible and the feature is related to operating system functions, try to check it on Windows and Linux.
|
||||
Calibre-Web is automatically tested on Linux in combination with python 3.7. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests.
|
||||
Please check if your code runs with python 3, python 2 is no longer supported. If possible and the feature is related to operating system functions, try to check it on Windows and Linux.
|
||||
Calibre-Web is automatically tested on Linux in combination with python 3.8. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests.
|
||||
A static code analysis is done by Codacy, but it's partly broken and doesn't run automatically. You could check your code with ESLint before contributing, a configuration file can be found in the projects root folder.
|
||||
|
|
|
@ -102,8 +102,9 @@ def create_app():
|
|||
|
||||
log.info('Starting Calibre Web...')
|
||||
if sys.version_info < (3, 0):
|
||||
log.info('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3')
|
||||
print('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3')
|
||||
log.info('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***')
|
||||
print('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***')
|
||||
sys.exit(5)
|
||||
Principal(app)
|
||||
lm.init_app(app)
|
||||
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
||||
|
|
|
@ -34,6 +34,7 @@ from babel.dates import format_datetime
|
|||
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response
|
||||
from flask_login import login_required, current_user, logout_user, confirm_login
|
||||
from flask_babel import gettext as _
|
||||
from flask import session as flask_session
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
|
||||
|
@ -98,8 +99,11 @@ def admin_required(f):
|
|||
|
||||
@admi.before_app_request
|
||||
def before_request():
|
||||
# make remember me function work
|
||||
if current_user.is_authenticated:
|
||||
confirm_login()
|
||||
if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path:
|
||||
logout_user()
|
||||
g.constants = constants
|
||||
g.user = current_user
|
||||
g.allow_registration = config.config_public_reg
|
||||
|
@ -1796,7 +1800,7 @@ def import_ldap_users():
|
|||
|
||||
|
||||
def extract_user_data_from_field(user, field):
|
||||
match = re.search(field + r"=([\d\s\w-]+)", user, re.IGNORECASE | re.UNICODE)
|
||||
match = re.search(field + r"=([\.\d\s\w-]+)", user, re.IGNORECASE | re.UNICODE)
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
|
|
|
@ -39,7 +39,9 @@ def _get_command_version(path, pattern, argument=None):
|
|||
if argument:
|
||||
command.append(argument)
|
||||
try:
|
||||
return process_wait(command, pattern=pattern).string
|
||||
match = process_wait(command, pattern=pattern)
|
||||
if isinstance(match, re.Match):
|
||||
return match.string
|
||||
except Exception as ex:
|
||||
log.warning("%s: %s", path, ex)
|
||||
return _EXECUTION_ERROR
|
||||
|
|
|
@ -692,6 +692,10 @@ class CalibreDB():
|
|||
query = self.session.query(database)
|
||||
if len(join) == 6:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4]).outerjoin(join[5])
|
||||
if len(join) == 5:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4])
|
||||
if len(join) == 4:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3])
|
||||
if len(join) == 3:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
||||
elif len(join) == 2:
|
||||
|
|
|
@ -3293,6 +3293,10 @@ div.btn-group[role=group][aria-label="Download, send to Kindle, reading"] .dropd
|
|||
left: 0 !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#add-to-shelves {
|
||||
max-height: calc(100% - 120px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dropdown-menu > li > a {
|
||||
color: hsla(0, 0%, 100%, .7);
|
||||
|
|
|
@ -706,7 +706,7 @@ $(function() {
|
|||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../../ajax/view",
|
||||
url: window.location.pathname + "/../ajax/view",
|
||||
data: "{\"series\": {\"series_view\": \""+ view +"\"}}",
|
||||
success: function success() {
|
||||
location.reload();
|
||||
|
|
|
@ -74,7 +74,12 @@ $(function() {
|
|||
});
|
||||
});
|
||||
|
||||
$("#merge_books").click(function() {
|
||||
$("#merge_books").click(function(event) {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
$('#mergeModal').modal("show");
|
||||
}
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<h2 class="{{page}}">{{_(title)}}</h2>
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="row form-group">
|
||||
<div class="btn btn-default disabled" id="merge_books" data-toggle="modal" data-target="#mergeModal" aria-disabled="true">{{_('Merge selected books')}}</div>
|
||||
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true">{{_('Merge selected books')}}</div>
|
||||
<div class="btn btn-default disabled" id="delete_selection" aria-disabled="true">{{_('Remove Selections')}}</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >
|
||||
<label for="config_use_google_drive">{{_('Use Google Drive?')}}</label>
|
||||
</div>
|
||||
{% if not gdriveError %}
|
||||
{% if not gdriveError and config.config_use_google_drive %}
|
||||
{% if show_authenticate_google_drive and config.config_use_google_drive %}
|
||||
<div class="form-group required">
|
||||
<a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
|
||||
|
|
|
@ -122,7 +122,8 @@
|
|||
{% endif %}
|
||||
|
||||
{% if entry.series|length > 0 %}
|
||||
<p>{{_('Book')}} {{entry.series_index|formatfloat(2)}} {{_('of')}} <a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p>
|
||||
<p>{{_("Book %(index)s of %(range)s", index=entry.series_index|formatfloat(2), range=("<a href='" + url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id) + "'>" + entry.series[0].name + "</a>")|safe) }}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if entry.languages.__len__() > 0 %}
|
||||
|
|
|
@ -18,8 +18,7 @@
|
|||
<button class="btn btn-primary char">{{char.char}}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<button class="update-view btn btn-primary" href="#" data-target="series_view" id='list-button' data-view="list">List</button>
|
||||
<button class="update-view btn btn-primary" data-target="series_view" id="list-button" data-view="list">List</button>
|
||||
</div>
|
||||
|
||||
{% if entries[0] %}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
|
||||
{% if data == "series" %}
|
||||
<button class="update-view btn btn-primary" href="#" data-target="series_view" id='grid-button' data-view="grid">Grid</button>
|
||||
<button class="update-view btn btn-primary" data-target="series_view" id="grid-button" data-view="grid">Grid</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="container">
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-7">
|
||||
{% if log_enable %}
|
||||
<a class="btn btn-default" id="log_file" href="{{url_for('admin.download_log', logtype=0)}}">{{_('Download Calibre-Web Log')}}</a>
|
||||
<a class="btn btn-default" id="log_file_0" href="{{url_for('admin.download_log', logtype=0)}}">{{_('Download Calibre-Web Log')}}</a>
|
||||
{% endif %}
|
||||
{% if accesslog_enable %}
|
||||
<a class="btn btn-default" id="log_file" href="{{url_for('admin.download_log', logtype=1)}}">{{_('Download Access Log')}}</a>
|
||||
<a class="btn btn-default" id="log_file_1" href="{{url_for('admin.download_log', logtype=1)}}">{{_('Download Access Log')}}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
{{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelves'), visiblility, all_roles)}}
|
||||
{% if kobo_support %}
|
||||
{{ user_single_checkbox_row("kobo_only_shelves_sync", _('Sync Selected Shelves with Kobo'))}}
|
||||
{{ user_single_checkbox_row("kobo_only_shelves_sync", _('Sync selected Shelves with Kobo'))}}
|
||||
{% endif %}
|
||||
{{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}}
|
||||
{{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}}
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
45
cps/ub.py
45
cps/ub.py
|
@ -27,6 +27,8 @@ from flask import session as flask_session
|
|||
from binascii import hexlify
|
||||
|
||||
from flask_login import AnonymousUserMixin, current_user
|
||||
from flask_login import user_logged_in
|
||||
from contextlib import contextmanager
|
||||
|
||||
try:
|
||||
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
||||
|
@ -61,6 +63,36 @@ Base = declarative_base()
|
|||
searched_ids = {}
|
||||
|
||||
|
||||
def signal_store_user_session(object, user):
|
||||
store_user_session()
|
||||
|
||||
def store_user_session():
|
||||
if flask_session.get('_user_id', ""):
|
||||
try:
|
||||
if not check_user_session(flask_session.get('_user_id', ""), flask_session.get('_id', "")):
|
||||
user_session = User_Sessions(flask_session.get('_user_id', ""), flask_session.get('_id', ""))
|
||||
session.add(user_session)
|
||||
session.commit()
|
||||
except (exc.OperationalError, exc.InvalidRequestError):
|
||||
session.rollback()
|
||||
# log.debug(flask_session.get('_id', ""))
|
||||
|
||||
def delete_user_session(user_id, session_key):
|
||||
try:
|
||||
# log.debug(session_key)
|
||||
session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
|
||||
User_Sessions.session_key==session_key).delete()
|
||||
session.commit()
|
||||
except (exc.OperationalError, exc.InvalidRequestError):
|
||||
session.rollback()
|
||||
|
||||
|
||||
def check_user_session(user_id, session_key):
|
||||
return bool(session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
|
||||
User_Sessions.session_key==session_key).one_or_none())
|
||||
|
||||
user_logged_in.connect(signal_store_user_session)
|
||||
|
||||
def store_ids(result):
|
||||
ids = list()
|
||||
for element in result:
|
||||
|
@ -72,7 +104,7 @@ class UserBase:
|
|||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
return self.is_active
|
||||
|
||||
def _has_role(self, role_flag):
|
||||
return constants.has_flag(self.role, role_flag)
|
||||
|
@ -261,6 +293,17 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||
flask_session['view'][page][prop] = value
|
||||
return None
|
||||
|
||||
class User_Sessions(Base):
|
||||
__tablename__ = 'user_session'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('user.id'))
|
||||
session_key = Column(String, default="")
|
||||
|
||||
def __init__(self, user_id, session_key):
|
||||
self.user_id = user_id
|
||||
self.session_key = session_key
|
||||
|
||||
|
||||
# Baseclass representing Shelfs in calibre-web in app.db
|
||||
class Shelf(Base):
|
||||
|
|
|
@ -21,7 +21,8 @@ import binascii
|
|||
|
||||
from sqlalchemy.sql.expression import func
|
||||
from werkzeug.security import check_password_hash
|
||||
from flask_login import login_required
|
||||
from flask_login import login_required, login_user
|
||||
|
||||
|
||||
from . import lm, ub, config, constants, services
|
||||
|
||||
|
@ -58,6 +59,7 @@ def load_user_from_request(request):
|
|||
if rp_header_username:
|
||||
user = _fetch_user_by_name(rp_header_username)
|
||||
if user:
|
||||
login_user(user)
|
||||
return user
|
||||
|
||||
auth_header = request.headers.get("Authorization")
|
||||
|
|
15
cps/web.py
15
cps/web.py
|
@ -423,7 +423,11 @@ def render_rated_books(page, book_id, order):
|
|||
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
|
||||
db.Books,
|
||||
db.Books.ratings.any(db.Ratings.rating > 9),
|
||||
order)
|
||||
order,
|
||||
db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book,
|
||||
db.Series)
|
||||
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
id=book_id, title=_(u"Top Rated Books"), page="rated")
|
||||
else:
|
||||
|
@ -629,6 +633,9 @@ def render_read_books(page, are_read, as_xml=False, order=None):
|
|||
db.Books,
|
||||
db_filter,
|
||||
order,
|
||||
db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book,
|
||||
db.Series,
|
||||
ub.ReadBook, db.Books.id == ub.ReadBook.book_id)
|
||||
else:
|
||||
try:
|
||||
|
@ -640,6 +647,9 @@ def render_read_books(page, are_read, as_xml=False, order=None):
|
|||
db.Books,
|
||||
db_filter,
|
||||
order,
|
||||
db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book,
|
||||
db.Series,
|
||||
db.cc_classes[config.config_read_column])
|
||||
except (KeyError, AttributeError):
|
||||
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
||||
|
@ -787,7 +797,7 @@ def list_books():
|
|||
db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
|
||||
elif sort == "languages":
|
||||
order = [db.Languages.lang_code.asc()] if order == "asc" else [db.Languages.lang_code.desc()]
|
||||
join = db.books_languages_link,db.Books.id == db.books_languages_link.c.book, db.Languages
|
||||
join = db.books_languages_link, db.Books.id == db.books_languages_link.c.book, db.Languages
|
||||
elif order and sort in ["sort", "title", "authors_sort", "series_index"]:
|
||||
order = [text(sort + " " + order)]
|
||||
elif not state:
|
||||
|
@ -1582,6 +1592,7 @@ def login():
|
|||
@login_required
|
||||
def logout():
|
||||
if current_user is not None and current_user.is_authenticated:
|
||||
ub.delete_user_session(current_user.id, flask_session.get('_id',""))
|
||||
logout_user()
|
||||
if feature_support['oauth'] and (config.config_login_type == 2 or config.config_login_type == 3):
|
||||
logout_oauth_user()
|
||||
|
|
427
messages.pot
427
messages.pot
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user