1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-24 02:27:22 +00:00
This commit is contained in:
xlivevil 2021-08-05 21:39:04 +08:00
commit fb97e39d9f
56 changed files with 5275 additions and 4964 deletions

View File

@ -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.

View File

@ -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))

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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();

View File

@ -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",

View File

@ -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">

View File

@ -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>

View File

@ -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 %}

View File

@ -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] %}

View File

@ -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">

View File

@ -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>

View File

@ -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)}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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):

View File

@ -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")

View File

@ -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()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff