Commit be5c67fd authored by Ozzieisaacs's avatar Ozzieisaacs

Merge branch 'master' into Develop

parents 2bd4eff5 fc4dc36c
......@@ -31,7 +31,10 @@ from flask_babel import gettext as _
from . import db, converter, uploader, server, isoLanguages
from .web import render_title_template
try:
from flask_login import __version__ as flask_loginVersion
except ImportError:
from flask_login.__about__ import __version__ as flask_loginVersion
about = flask.Blueprint('about', __name__)
......@@ -40,7 +43,7 @@ _VERSIONS = OrderedDict(
Python=sys.version,
WebServer=server.VERSION,
Flask=flask.__version__,
Flask_Login=flask_login.__version__,
Flask_Login=flask_loginVersion,
Flask_Principal=flask_principal.__version__,
Werkzeug=werkzeug.__version__,
Babel=babel.__version__,
......
......@@ -45,7 +45,7 @@ from .gdriveutils import is_gdrive_ready, gdrive_support
from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
feature_support = {
'ldap': bool(services.ldap),
'ldap': False, # bool(services.ldap),
'goodreads': bool(services.goodreads)
}
......@@ -56,10 +56,11 @@ feature_support = {
# feature_support['rar'] = False
try:
from .oauth_bb import oauth_check
from .oauth_bb import oauth_check, oauthblueprints
feature_support['oauth'] = True
except ImportError:
feature_support['oauth'] = False
oauthblueprints = []
oauth_check = {}
......@@ -343,18 +344,27 @@ def _configuration_update_helper():
_config_int("config_updatechannel")
# GitHub OAuth configuration
if config.config_login_type == constants.LOGIN_OAUTH_GITHUB:
_config_string("config_github_oauth_client_id")
_config_string("config_github_oauth_client_secret")
if not config.config_github_oauth_client_id or not config.config_github_oauth_client_secret:
return _configuration_result('Please enter Github oauth credentials', gdriveError)
# Google OAuth configuration
if config.config_login_type == constants.LOGIN_OAUTH_GOOGLE:
_config_string("config_google_oauth_client_id")
_config_string("config_google_oauth_client_secret")
if not config.config_google_oauth_client_id or not config.config_google_oauth_client_secret:
return _configuration_result('Please enter Google oauth credentials', gdriveError)
if config.config_login_type == constants.LOGIN_OAUTH:
active_oauths = 0
for element in oauthblueprints:
if to_save["config_"+str(element['id'])+"_oauth_client_id"] \
and to_save["config_"+str(element['id'])+"_oauth_client_secret"]:
active_oauths += 1
element["active"] = 1
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
{"oauth_client_id":to_save["config_"+str(element['id'])+"_oauth_client_id"],
"oauth_client_secret":to_save["config_"+str(element['id'])+"_oauth_client_secret"],
"active":1})
if to_save["config_" + str(element['id']) + "_oauth_client_id"] != element['oauth_client_id'] \
or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']:
reboot_required = True
element['oauth_client_id'] = to_save["config_"+str(element['id'])+"_oauth_client_id"]
element['oauth_client_secret'] = to_save["config_"+str(element['id'])+"_oauth_client_secret"]
else:
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
{"active":0})
element["active"] = 0
_config_int("config_log_level")
_config_string("config_logfile")
......@@ -410,7 +420,7 @@ def _configuration_result(error_flash=None, gdriveError=None):
flash(_(error_flash), category="error")
show_login_button = False
return render_title_template("config_edit.html", config=config,
return render_title_template("config_edit.html", config=config, provider=oauthblueprints,
show_back_button=show_back_button, show_login_button=show_login_button,
show_authenticate_google_drive=gdrive_authenticate,
gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support,
......@@ -653,7 +663,7 @@ def send_logfile(logtype):
@admi.route("/get_update_status", methods=['GET'])
@login_required_if_no_ano
def get_update_status():
return updater_thread.get_available_updates(request.method)
return updater_thread.get_available_updates(request.method, locale=get_locale())
@admi.route("/get_updater_status", methods=['GET', 'POST'])
......
......@@ -31,7 +31,7 @@ try:
from comicapi.comicarchive import ComicArchive, MetaDataStyle
use_comic_meta = True
except ImportError as e:
log.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
log.debug('cannot import comicapi, extracting comic metadata will not work: %s', e)
import zipfile
import tarfile
use_comic_meta = False
......
......@@ -20,6 +20,7 @@
from __future__ import division, print_function, unicode_literals
import os
import json
import sys
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean
from sqlalchemy.ext.declarative import declarative_base
......@@ -43,41 +44,48 @@ class _Settings(_Base):
mail_login = Column(String, default='mail@example.com')
mail_password = Column(String, default='mypassword')
mail_from = Column(String, default='automailer <mail@example.com>')
config_calibre_dir = Column(String)
config_port = Column(Integer, default=constants.DEFAULT_PORT)
config_certfile = Column(String)
config_keyfile = Column(String)
config_calibre_web_title = Column(String, default=u'Calibre-Web')
config_books_per_page = Column(Integer, default=60)
config_random_books = Column(Integer, default=4)
config_authors_max = Column(Integer, default=0)
config_read_column = Column(Integer, default=0)
config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
config_mature_content_tags = Column(String, default='')
config_theme = Column(Integer, default=0)
config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
config_logfile = Column(String)
config_access_log = Column(SmallInteger, default=0)
config_access_logfile = Column(String)
config_uploading = Column(SmallInteger, default=0)
config_anonbrowse = Column(SmallInteger, default=0)
config_public_reg = Column(SmallInteger, default=0)
config_remote_login = Column(Boolean, default=False)
config_default_role = Column(SmallInteger, default=0)
config_default_show = Column(SmallInteger, default=6143)
config_columns_to_ignore = Column(String)
config_use_google_drive = Column(Boolean, default=False)
config_google_drive_folder = Column(String)
config_google_drive_watch_changes_response = Column(String)
config_remote_login = Column(Boolean, default=False)
config_use_goodreads = Column(Boolean, default=False)
config_goodreads_api_key = Column(String)
config_goodreads_api_secret = Column(String)
config_login_type = Column(Integer, default=0)
# config_use_ldap = Column(Boolean)
config_ldap_provider_url = Column(String)
config_ldap_dn = Column(String)
# config_use_github_oauth = Column(Boolean)
config_github_oauth_client_id = Column(String)
config_github_oauth_client_secret = Column(String)
# config_use_google_oauth = Column(Boolean)
config_google_oauth_client_id = Column(String)
config_google_oauth_client_secret = Column(String)
# config_oauth_provider = Column(Integer)
config_ldap_provider_url = Column(String, default='localhost')
config_ldap_port = Column(SmallInteger, default=389)
config_ldap_schema = Column(String, default='ldap')
......@@ -90,14 +98,12 @@ class _Settings(_Base):
config_ldap_dn = Column(String)
config_ldap_user_object = Column(String)
config_ldap_openldap = Column(Boolean, default=False)
config_mature_content_tags = Column(String, default='')
config_logfile = Column(String)
config_access_logfile = Column(String)
config_ebookconverter = Column(Integer, default=0)
config_converterpath = Column(String)
config_calibre = Column(String)
config_rarfile_location = Column(String)
config_theme = Column(Integer, default=0)
config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
def __repr__(self):
......@@ -106,6 +112,7 @@ class _Settings(_Base):
# Class holds all application specific settings in calibre-web
class _ConfigSQL(object):
# pylint: disable=no-member
def __init__(self, session):
self._session = session
self._settings = None
......@@ -264,8 +271,15 @@ def _migrate_table(session, orm_class):
session.query(column).first()
except exc.OperationalError as err:
log.debug("%s: %s", column_name, err)
if column.default is not None:
if sys.version_info < (3, 0):
if isinstance(column.default.arg,unicode):
column.default.arg = column.default.arg.encode('utf-8')
column_default = "" if column.default is None else ("DEFAULT %r" % column.default.arg)
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, column_name, column.type, column_default)
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
column_name,
column.type,
column_default)
log.debug(alter_table)
session.execute(alter_table)
changed = True
......@@ -273,6 +287,17 @@ def _migrate_table(session, orm_class):
if changed:
session.commit()
def autodetect_calibre_binary():
if sys.platform == "win32":
calibre_path = ["C:\\program files\calibre\calibre-convert.exe",
"C:\\program files(x86)\calibre\calibre-convert.exe"]
else:
calibre_path = ["/opt/calibre/ebook-convert"]
for element in calibre_path:
if os.path.isfile(element) and os.access(element, os.X_OK):
return element
return None
def _migrate_database(session):
# make sure the table is created, if it does not exist
......
......@@ -21,6 +21,7 @@ import sys
import os
from collections import namedtuple
HOME_CONFIG = False
# Base dir is parent of current file, necessary if called from different folder
if sys.version_info < (3, 0):
......@@ -32,7 +33,14 @@ else:
STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static')
TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')
CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', BASE_DIR)
if HOME_CONFIG:
home_dir = os.path.join(os.path.expanduser("~"),".calibre-web")
if not os.path.exists(home_dir):
os.makedirs(home_dir)
CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', home_dir)
else:
CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', BASE_DIR)
ROLE_USER = 0 << 0
......@@ -83,8 +91,8 @@ AUTO_UPDATE_NIGHTLY = 1 << 2
LOGIN_STANDARD = 0
LOGIN_LDAP = 1
LOGIN_OAUTH_GITHUB = 2
LOGIN_OAUTH_GOOGLE = 3
LOGIN_OAUTH = 2
# LOGIN_OAUTH_GOOGLE = 3
DEFAULT_PASSWORD = "admin123"
......@@ -116,7 +124,7 @@ def selected_roles(dictionary):
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
'series_id, languages')
STABLE_VERSION = {'version': '0.6.4 Beta'}
STABLE_VERSION = {'version': '0.6.5 Beta'}
NIGHTLY_VERSION = {}
NIGHTLY_VERSION[0] = '$Format:%H$'
......
......@@ -39,12 +39,12 @@ except ImportError:
gdrive_support = False
from . import logger, cli, config
from .constants import BASE_DIR as _BASE_DIR
from .constants import CONFIG_DIR as _CONFIG_DIR
SETTINGS_YAML = os.path.join(_BASE_DIR, 'settings.yaml')
CREDENTIALS = os.path.join(_BASE_DIR, 'gdrive_credentials')
CLIENT_SECRETS = os.path.join(_BASE_DIR, 'client_secrets.json')
SETTINGS_YAML = os.path.join(_CONFIG_DIR, 'settings.yaml')
CREDENTIALS = os.path.join(_CONFIG_DIR, 'gdrive_credentials')
CLIENT_SECRETS = os.path.join(_CONFIG_DIR, 'client_secrets.json')
log = logger.create()
......
......@@ -71,14 +71,6 @@ from .worker import TASK_EMAIL, TASK_CONVERT, TASK_UPLOAD, TASK_CONVERT_ANY
log = logger.create()
def update_download(book_id, user_id):
check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id ==
book_id).first()
if not check:
new_download = ub.Downloads(user_id=user_id, book_id=book_id)
ub.session.add(new_download)
ub.session.commit()
# Convert existing book entry to new format
def convert_book_format(book_id, calibrepath, old_book_format, new_book_format, user_id, kindle_mail=None):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
......@@ -563,6 +555,7 @@ def check_unrar(unrarLocation):
try:
if sys.version_info < (3, 0):
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
unrarLocation = [unrarLocation]
for lines in process_wait(unrarLocation):
value = re.search('UNRAR (.*) freeware', lines)
if value:
......
......@@ -23,7 +23,7 @@ import logging
from logging import Formatter, StreamHandler
from logging.handlers import RotatingFileHandler
from .constants import BASE_DIR as _BASE_DIR
from .constants import CONFIG_DIR as _CONFIG_DIR
ACCESS_FORMATTER_GEVENT = Formatter("%(message)s")
......@@ -31,8 +31,8 @@ ACCESS_FORMATTER_TORNADO = Formatter("[%(asctime)s] %(message)s")
FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d} %(message)s")
DEFAULT_LOG_LEVEL = logging.INFO
DEFAULT_LOG_FILE = os.path.join(_BASE_DIR, "calibre-web.log")
DEFAULT_ACCESS_LOG = os.path.join(_BASE_DIR, "access.log")
DEFAULT_LOG_FILE = os.path.join(_CONFIG_DIR, "calibre-web.log")
DEFAULT_ACCESS_LOG = os.path.join(_CONFIG_DIR, "access.log")
LOG_TO_STDERR = '/dev/stderr'
logging.addLevelName(logging.WARNING, "WARN")
......@@ -76,7 +76,7 @@ def is_valid_logfile(file_path):
def _absolute_log_file(log_file, default_log_file):
if log_file:
if not os.path.dirname(log_file):
log_file = os.path.join(_BASE_DIR, log_file)
log_file = os.path.join(_CONFIG_DIR, log_file)
return os.path.abspath(log_file)
return default_log_file
......@@ -95,6 +95,11 @@ def setup(log_file, log_level=None):
Configure the logging output.
May be called multiple times.
'''
# if debugging, start logging to stderr immediately
if os.environ.get('FLASK_DEBUG', None):
log_file = LOG_TO_STDERR
log_level = logging.DEBUG
log_file = _absolute_log_file(log_file, DEFAULT_LOG_FILE)
log_level = log_level or DEFAULT_LOG_LEVEL
......@@ -162,8 +167,3 @@ class StderrLogger(object):
self.buffer += message
except Exception:
self.log.debug("Logging Error")
# if debugging, start logging to stderr immediately
if os.environ.get('FLASK_DEBUG', None):
setup(LOG_TO_STDERR, logging.DEBUG)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2018-2019 jim3ma
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
from __future__ import division, print_function, unicode_literals
from flask import session
......@@ -16,13 +32,14 @@ try:
.. _SQLAlchemy: http://www.sqlalchemy.org/
"""
def __init__(self, model, session,
def __init__(self, model, session, provider_id,
user=None, user_id=None, user_required=None, anon_user=None,
cache=None):
self.provider_id = provider_id
super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache)
def get(self, blueprint, user=None, user_id=None):
if blueprint.name + '_oauth_token' in session and session[blueprint.name + '_oauth_token'] != '':
if self.provider_id + '_oauth_token' in session and session[self.provider_id + '_oauth_token'] != '':
return session[blueprint.name + '_oauth_token']
# check cache
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
......@@ -33,15 +50,15 @@ try:
# if not cached, make database queries
query = (
self.session.query(self.model)
.filter_by(provider=blueprint.name)
.filter_by(provider=self.provider_id)
)
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
u = first(_get_real_user(ref, self.anon_user)
for ref in (user, self.user, blueprint.config.get("user")))
use_provider_user_id = False
if blueprint.name + '_oauth_user_id' in session and session[blueprint.name + '_oauth_user_id'] != '':
query = query.filter_by(provider_user_id=session[blueprint.name + '_oauth_user_id'])
if self.provider_id + '_oauth_user_id' in session and session[self.provider_id + '_oauth_user_id'] != '':
query = query.filter_by(provider_user_id=session[self.provider_id + '_oauth_user_id'])
use_provider_user_id = True
if self.user_required and not u and not uid and not use_provider_user_id:
......@@ -78,7 +95,7 @@ try:
# if there was an existing model, delete it
existing_query = (
self.session.query(self.model)
.filter_by(provider=blueprint.name)
.filter_by(provider=self.provider_id)
)
# check for user ID
has_user_id = hasattr(self.model, "user_id")
......@@ -92,7 +109,7 @@ try:
existing_query.delete()
# create a new model for this token
kwargs = {
"provider": blueprint.name,
"provider": self.provider_id,
"token": token,
}
if has_user_id and uid:
......@@ -110,7 +127,7 @@ try:
def delete(self, blueprint, user=None, user_id=None):
query = (
self.session.query(self.model)
.filter_by(provider=blueprint.name)
.filter_by(provider=self.provider_id)
)
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
u = first(_get_real_user(ref, self.anon_user)
......
This diff is collapsed.
......@@ -47,8 +47,6 @@ def init_app(app, config):
app.config['LDAP_USE_TLS'] = bool(config.config_ldap_use_tls)
app.config['LDAP_OPENLDAP'] = bool(config.config_ldap_openldap)
# app.config['LDAP_BASE_DN'] = 'ou=users,dc=yunohost,dc=org'
# app.config['LDAP_USER_OBJECT_FILTER'] = '(uid=%s)'
_ldap.init_app(app)
......
......@@ -141,6 +141,7 @@ input.pill:not(:checked) + label .glyphicon {
.filterheader { margin-bottom: 20px; }
.errorlink {margin-top: 20px;}
.modal-body .comments {
max-height:300px;
overflow-y: auto;
......
......@@ -37,10 +37,11 @@ $(document).on("change", "select[data-control]", function() {
var showOrHide = parseInt($this.val());
// var showOrHideLast = $("#" + name + " option:last").val()
for (var i = 0; i < $(this)[0].length; i++) {
if (parseInt($(this)[0][i].value) === showOrHide) {
$("[data-related=\"" + name + "-" + i + "\"]").show();
var element = parseInt($(this)[0][i].value);
if (element === showOrHide) {
$("[data-related=" + name + "-" + element + "]").show();
} else {
$("[data-related=\"" + name + "-" + i + "\"]").hide();
$("[data-related=" + name + "-" + element + "]").hide();
}
}
});
......
......@@ -19,6 +19,7 @@
<label for="config_calibre_dir">{{_('Location of Calibre database')}}</label>
<input type="text" class="form-control" name="config_calibre_dir" id="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
</div>
{% if feature_support['gdrive'] %}
<div class="form-group required">
<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>
......@@ -66,6 +67,7 @@
{% endif %}
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
......@@ -193,8 +195,7 @@
<option value="1" {% if config.config_login_type == 1 %}selected{% endif %}>{{_('Use LDAP Authentication')}}</option>
{% endif %}
{% if feature_support['oauth'] %}
<option value="2" {% if config.config_login_type == 2 %}selected{% endif %}>{{_('Use GitHub OAuth')}}</option>
<option value="3" {% if config.config_login_type == 3 %}selected{% endif %}>{{_('Use Google OAuth')}}</option>
<option value="2" {% if config.config_login_type == 2 %}selected{% endif %}>{{_('Use OAuth')}}</option>
{% endif %}
</select>
</div>
......@@ -254,30 +255,19 @@
{% endif %}
{% if feature_support['oauth'] %}
<div data-related="login-settings-2">
{% for prov in provider %}
<div class="form-group">
<a href="https://github.com/settings/developers" target="_blank">{{_('Obtain GitHub OAuth Credential')}}</a>
</div>
<div class="form-group">
<label for="config_github_oauth_client_id">{{_('GitHub OAuth Client Id')}}</label>
<input type="text" class="form-control" id="config_github_oauth_client_id" name="config_github_oauth_client_id" value="{% if config.config_github_oauth_client_id != None %}{{ config.config_github_oauth_client_id }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_github_oauth_client_secret">{{_('GitHub OAuth Client Secret')}}</label>
<input type="text" class="form-control" id="config_github_oauth_client_secret" name="config_github_oauth_client_secret" value="{% if config.config_github_oauth_client_secret != None %}{{ config.config_github_oauth_client_secret }}{% endif %}" autocomplete="off">
</div>
</div>
<div data-related="login-settings-3">
<div class="form-group">
<a href="https://console.developers.google.com/apis/credentials" target="_blank">{{_('Obtain Google OAuth Credential')}}</a>
<a href="{{prov['obtain_link']}}" target="_blank">{{_('Obtain %(provider)s OAuth Credential', provider=prov['provider_name'])}}</a>
</div>
<div class="form-group">
<label for="config_google_oauth_client_id">{{_('Google OAuth Client Id')}}</label>
<input type="text" class="form-control" id="config_google_oauth_client_id" name="config_google_oauth_client_id" value="{% if config.config_google_oauth_client_id != None %}{{ config.config_google_oauth_client_id }}{% endif %}" autocomplete="off">
<label for="config_{{ prov['id'] }}_oauth_client_id">{{_('%(provider)s OAuth Client Id', provider=prov['provider_name'])}}</label>
<input type="text" class="form-control" id="config_{{ prov['id'] }}_oauth_client_id" name="config_{{ prov['id'] }}_oauth_client_id" value="{% if prov['oauth_client_id']%}{{ prov['oauth_client_id'] }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_google_oauth_client_secret">{{_('Google OAuth Client Secret')}}</label>
<input type="text" class="form-control" id="config_google_oauth_client_secret" name="config_google_oauth_client_secret" value="{% if config.config_google_oauth_client_secret != None %}{{ config.config_google_oauth_client_secret }}{% endif %}" autocomplete="off">
<label for="config_{{ prov['id'] }}_oauth_client_secret">{{_('%(provider)s OAuth Client Secret', provider=prov['provider_name'])}}</label>
<input type="text" class="form-control" id="config_{{ prov['id'] }}_oauth_client_secret" name="config_{{ prov['id'] }}_oauth_client_secret" value="{% if prov['oauth_client_secret']%}{{ prov['oauth_client_secret'] }}{% endif %}" autocomplete="off">
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
......
......@@ -17,10 +17,34 @@
{% endif %}
</head>
<body>
<div class="text-center">
<h1>{{ error_code }}</h1>
<div class="container-fluid">
<div class="row">
<div class="col">
<h1 class="text-center">{{ error_code }}</h1>
<h3>{{ error_name }}</h3>
</div>
</div>
<div class="row">
<div class="col-md-offset-4 text-left">
{% for element in error_stack %}
<div>{{ element }}</div>
{% endfor %}
</div>
</div>
{% if issue %}
<div class="row">
<div class="col errorlink">Please report this issue with all related information:
<a href="https://github.com/janeczku/calibre-web/issues/new">{{_('Create issue')}}</a>
</div>
</div>
{% endif %}
<div class="row">
<div class="col errorlink">
<a href="{{url_for('web.index')}}" title="{{ _('Back to home') }}">{{_('Back to home')}}</a>
</div>
</div>
</div>
</body>
</html>
......@@ -10,9 +10,10 @@
{% endif %}
<div class="row">
<div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{lang_counter[loop.index0].bookcount}}</span></div>
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{{url_for('web.language', name=lang.lang_code)}}">{{lang.name}}</a></div>
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{{url_for('web.books_list', book_id=lang.lang_code, data=data, sort='new')}}">{{lang.name}}</a></div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
......@@ -79,15 +79,6 @@
</div>
<div class="overlay"></div>
<script type="text/javascript">
window.calibre = {
filePath: "{{ url_for('static', filename='js/libs/') }}",
cssPath: "{{ url_for('static', filename='css/') }}",
bookmarkUrl: "{{ url_for('bookmark', book_id=bookid, book_format='EPUB') }}",
bookUrl: "{{ url_for('get_download_link_ext', book_id=bookid, book_format="epub", anyname='file.epub') }}",
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
useBookmarks: {{ g.user.is_authenticated | tojson }} };
</script>
<script src="{{ url_for('static', filename='js/libs/jszip.min.js') }}">
</script> <script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
<script type="text/javascript">
......
......@@ -47,13 +47,13 @@
</div>
{% if registered_oauth.keys()| length > 0 %}
{% for oauth, name in registered_oauth.iteritems() %}
{% for id, name in registered_oauth.items() %}
<div class="form-group">
<label>{{ name }} {{_('OAuth Settings')}}</label>
{% if oauth not in oauth_status %}
<div><a href="{{ url_for(oauth +'.login') }}" id="config_{{ oauth }}_oauth" class="btn btn-primary">{{_('Link')}}</a></div>
{% if id not in oauth_status %}
<div><a href="{{ url_for('oauth.'+ name +'_login') }}" id="config_{{ id }}_oauth" class="btn btn-primary">{{_('Link')}}</a></div>
{% else %}
<div><a href="{{ url_for('oauth.'+ oauth +'_login_unlink') }}" id="config_{{ oauth }}_oauth" class="btn btn-primary">{{_('Unlink')}}</a></div>
<div><a href="{{ url_for('oauth.'+ name +'_login_unlink') }}" id="config_{{ id }}_oauth" class="btn btn-primary">{{_('Unlink')}}</a></div>
{% endif %}
{% endfor %}
</div>
......
......@@ -180,6 +180,7 @@ class User(UserBase, Base):
default_language = Column(String(3), default="all")
mature_content = Column(Boolean, default=True)
if oauth_support:
class OAuth(OAuthConsumerMixin, Base):
provider_user_id = Column(String(256))
......@@ -187,6 +188,16 @@ if oauth_support:
user = relationship(User)
class OAuthProvider(Base):
__tablename__ = 'oauthProvider'
id = Column(Integer, primary_key=True)
provider_name = Column(String)
oauth_client_id = Column(String)
oauth_client_secret = Column(String)
active = Column(Boolean)
# Class for anonymous user is derived from User base and completly overrides methods and properties for the
# anonymous user
class Anonymous(AnonymousUserMixin, UserBase):
......
......@@ -33,7 +33,7 @@ import requests
from babel.dates import format_datetime
from flask_babel import gettext as _
from . import constants, logger, config, get_locale, web_server
from . import constants, logger, config, web_server
log = logger.create()
......@@ -62,10 +62,10 @@ class Updater(threading.Thread):
return self._stable_version_info()
return self._nightly_version_info()
def get_available_updates(self, request_method):
def get_available_updates(self, request_method, locale):
if config.config_updatechannel == constants.UPDATE_STABLE:
return self._stable_available_updates(request_method)
return self._nightly_available_updates(request_method)
return self._nightly_available_updates(request_method,locale)
def run(self):
try:
......@@ -239,7 +239,7 @@ class Updater(threading.Thread):
def _stable_version_info(cls):
return constants.STABLE_VERSION # Current version
def _nightly_available_updates(self, request_method):
def _nightly_available_updates(self, request_method, locale):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
if request_method == "GET":
repository_url = _REPOSITORY_API_URL
......@@ -288,7 +288,7 @@ class Updater(threading.Thread):
update_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
parents.append(
[
format_datetime(new_commit_date, format='short', locale=get_locale()),
format_datetime(new_commit_date, format='short', locale=locale),
update_data['message'],
update_data['sha']
]
......@@ -319,7 +319,7 @@ class Updater(threading.Thread):
parent_commit_date = datetime.datetime.strptime(
parent_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
parent_commit_date = format_datetime(
parent_commit_date, format='short', locale=get_locale())
parent_commit_date, format='short', locale=locale)
parents.append([parent_commit_date,
parent_data['message'].replace('\r\n', '<p>').replace('\n', '<p>')])
......@@ -331,7 +331,7 @@ class Updater(threading.Thread):
else:
# parent is our current version
break
status['history'] = parents[::-1]
else:
status['success'] = False
status['message'] = _(u'Could not fetch update information')
......
......@@ -42,7 +42,7 @@ try:
from wand.exceptions import PolicyError
use_generic_pdf_cover = False
except (ImportError, RuntimeError) as e:
log.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
log.debug('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
use_generic_pdf_cover = True
try:
......@@ -50,21 +50,21 @@ try:
from PyPDF2 import __version__ as PyPdfVersion
use_pdf_meta = True
except ImportError as e:
log.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
log.debug('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
use_pdf_meta = False
try:
from . import epub
use_epub_meta = True
except ImportError as e:
log.warning('cannot import epub, extracting epub metadata will not work: %s', e)
log.debug('cannot import epub, extracting epub metadata will not work: %s', e)
use_epub_meta = False
try:
from . import fb2
use_fb2_meta = True
except ImportError as e:
log.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
log.debug('cannot import fb2, extracting fb2 metadata will not work: %s', e)
use_fb2_meta = False
try:
......@@ -72,7 +72,7 @@ try:
from PIL import __version__ as PILversion
use_PIL = True
except ImportError as e:
log.warning('cannot import Pillow, using png and webp images as cover will not work: %s', e)
log.debug('cannot import Pillow, using png and webp images as cover will not work: %s', e)
use_generic_pdf_cover = True
use_PIL = False
......
......@@ -27,6 +27,8 @@ import base64
import datetime
import json
import mimetypes
import traceback
import sys
from babel import Locale as LC
from babel.dates import format_date
......@@ -52,7 +54,7 @@ from .pagination import Pagination
from .redirect import redirect_back
feature_support = {
'ldap': bool(services.ldap),
'ldap': False, # bool(services.ldap),
'goodreads': bool(services.goodreads)
}
......@@ -83,16 +85,30 @@ except ImportError:
# custom error page
def error_http(error):
return render_template('http_error.html',
error_code=error.code,
error_code="Error {0}".format(error.code),
error_name=error.name,
issue=False,
instance=config.config_calibre_web_title
), error.code
def internal_error(error):
__, __, tb = sys.exc_info()
return render_template('http_error.html',
error_code="Internal Server Error",
error_name=str(error),
issue=True,
error_stack=traceback.format_tb(tb),
instance=config.config_calibre_web_title
), 500
# http error handling
for ex in default_exceptions:
# new routine for all client errors, server errors stay
if ex < 500:
app.register_error_handler(ex, error_http)
elif ex == 500:
app.register_error_handler(ex, internal_error)
web = Blueprint('web', __name__)
......@@ -498,6 +514,8 @@ def books_list(data, sort, book_id, page):
return render_formats_books(page, book_id, order)
elif data == "category":
return render_category_books(page, book_id, order)
elif data == "language":
return render_language_books(page, book_id, order)
else:
entries, random, pagination = fill_indexpage(page, db.Books, True, order)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
......@@ -552,8 +570,8 @@ def render_author_books(page, author_id, order):
other_books = services.goodreads.get_other_books(author_info, entries)
return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id,
title=_(u"Author: %(name)s", name=author_name), author=author_info, other_books=other_books,
page="author")
title=_(u"Author: %(name)s", name=author_name), author=author_info,
other_books=other_books, page="author")
def render_publisher_books(page, book_id, order):
......@@ -574,19 +592,19 @@ def render_series_books(page, book_id, order):
if name:
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id),
[db.Books.series_index, order[0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"Series: %(serie)s", serie=name.name), page="series")
else:
abort(404)
def render_ratings_books(page, book_id, order):
if book_id <=5:
name = db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.id == book_id),
[db.Books.timestamp.desc(), order[0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
title=_(u"Rating: %(rating)s stars", rating=int(name.rating/2)), page="ratings")
name = db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.id == book_id),
[db.Books.timestamp.desc(), order[0]])
if name and name.rating <= 10:
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"Rating: %(rating)s stars", rating=int(name.rating/2)), page="ratings")
else:
abort(404)
......@@ -596,7 +614,7 @@ def render_formats_books(page, book_id, order):
if name:
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.data.any(db.Data.format == book_id.upper()),
[db.Books.timestamp.desc(), order[0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"File format: %(format)s", format=name.format), page="formats")
else:
abort(404)
......@@ -608,12 +626,27 @@ def render_category_books(page, book_id, order):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id),
[db.Series.name, db.Books.series_index, order[0]],
db.books_series_link, db.Series)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
title=_(u"Category: %(name)s", name=name.name), page="category")
else:
abort(404)
def render_language_books(page, name, order):
try:
cur_l = LC.parse(name)
lang_name = cur_l.get_language_name(get_locale())
except UnknownLocaleError:
try:
lang_name = _(isoLanguages.get(part3=name).name)
except KeyError:
abort(404)
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name),
[db.Books.timestamp.desc(), order[0]])
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
title=_(u"Language: %(name)s", name=lang_name), page="language")
@web.route("/author")
@login_required_if_no_ano
def author_list():
......@@ -714,29 +747,12 @@ def language_overview():
func.count('books_languages_link.book').label('bookcount')).group_by(
text('books_languages_link.lang_code')).all()
return render_title_template('languages.html', languages=languages, lang_counter=lang_counter,
charlist=charlist, title=_(u"Available languages"), page="langlist", data="language")
charlist=charlist, title=_(u"Available languages"), page="langlist",
data="language")
else:
abort(404)
@web.route("/language/<name>", defaults={'page': 1})
@web.route('/language/<name>/page/<int:page>')
@login_required_if_no_ano
def language(name, page):
try:
cur_l = LC.parse(name)
lang_name = cur_l.get_language_name(get_locale())
except UnknownLocaleError:
try:
lang_name = _(isoLanguages.get(part3=name).name)
except KeyError:
abort(404)
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name),
[db.Books.timestamp.desc()])
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Language: %(name)s", name=lang_name), page="language")
@web.route("/category")
@login_required_if_no_ano
def category_list():
......
This diff is collapsed.
......@@ -18,7 +18,11 @@ python-Levenshtein>=0.12.0
# ldap login
python_ldap>=3.0.0
flask-simpleldap
flask-simpleldap>1.3.0
#oauth
flask-dance>=0.13.0
sqlalchemy_utils>=0.33.5
# extracting metadata
lxml>=3.8.0
......@@ -27,7 +31,4 @@ rarfile>=2.7
# other
natsort>=2.2.0
# Oauth Login
flask-dance>=0.13.0
sqlalchemy_utils>=0.33.5
git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
......@@ -13,4 +13,3 @@ SQLAlchemy>=1.1.0
tornado>=4.1
Wand>=0.4.4
unidecode>=0.04.19
git+https://github.com/wildthyme/comicapi.git@cb279168f9c5cec742b5a05ac8326b9c168a8a91#egg=comicapi
......@@ -31,14 +31,14 @@ keywords =
calibre
calibre-web
library
python_requires = >=2.6
[options.entry_points]
console_scripts =
cps = calibreweb:main
[options]
python_requires = >=2.6
include_package_data = True
zip_safe = False
dependency_links = comicapi @ git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
install_requires =
Babel >= 1.3
Flask-Babel >= 0.11.1
......@@ -80,7 +80,8 @@ metadata =
Wand >= 0.4.4
comics=
natsort>=2.2.0
# https://github.com/wildthyme/comicapi/archive/cb279168f9c5cec742b5a05ac8326b9c168a8a91.zip#egg=comicapi
# comicapi @ git+https://github.com/wildthyme/comicapi.git@cb279168f9c5cec742b5a05ac8326b9c168a8a91#egg=comicapi
# find solution for this
# find solution for this should belong to comics
# comicapi @ git+https://github.com/OzzieIsaacs/comicapi/archive/5346716578b2843f54d522f44d01bc8d25001d24.zip#egg=comicapi
......@@ -20,6 +20,7 @@
# """Calibre-web distribution package setuptools installer."""
from setuptools import setup
from setuptools import find_packages
import os
import re
import codecs
......@@ -39,6 +40,7 @@ def find_version(*file_paths):
raise RuntimeError("Unable to find version string.")
setup(
packages=find_packages("src"),
package_dir = {'': 'src'},
version=find_version("src", "calibreweb", "cps", "constants.py")
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment