mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-24 02:27:22 +00:00
Added option to save books with no nenglish characters
Imporvements on metadata search
This commit is contained in:
parent
702e96ddd6
commit
ceec1051d5
@ -1208,6 +1208,7 @@ def _configuration_update_helper():
|
||||
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'))
|
||||
|
||||
_config_checkbox_int(to_save, "config_uploading")
|
||||
_config_checkbox_int(to_save, "config_unicode_filename")
|
||||
# Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case
|
||||
reboot_required |= (_config_checkbox_int(to_save, "config_anonbrowse")
|
||||
and config.config_login_type == constants.LOGIN_LDAP)
|
||||
|
@ -133,6 +133,7 @@ class _Settings(_Base):
|
||||
config_calibre = Column(String)
|
||||
config_rarfile_location = Column(String, default=None)
|
||||
config_upload_formats = Column(String, default=','.join(constants.EXTENSIONS_UPLOAD))
|
||||
config_unicode_filename =Column(Boolean, default=False)
|
||||
|
||||
config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
|
||||
|
||||
|
@ -230,16 +230,14 @@ def get_valid_filename(value, replace_whitespace=True):
|
||||
value = value[:-1]+u'_'
|
||||
value = value.replace("/", "_").replace(":", "_").strip('\0')
|
||||
if use_unidecode:
|
||||
value = (unidecode.unidecode(value))
|
||||
if config.config_unicode_filename:
|
||||
value = (unidecode.unidecode(value))
|
||||
else:
|
||||
value = value.replace(u'§', u'SS')
|
||||
value = value.replace(u'ß', u'ss')
|
||||
value = unicodedata.normalize('NFKD', value)
|
||||
re_slugify = re.compile(r'[\W\s-]', re.UNICODE)
|
||||
if isinstance(value, str): # Python3 str, Python2 unicode
|
||||
value = re_slugify.sub('', value)
|
||||
else:
|
||||
value = unicode(re_slugify.sub('', value))
|
||||
value = re_slugify.sub('', value)
|
||||
if replace_whitespace:
|
||||
# *+:\"/<>? are replaced by _
|
||||
value = re.sub(r'[*+:\\\"/<>?]+', u'_', value, flags=re.U)
|
||||
@ -248,10 +246,7 @@ def get_valid_filename(value, replace_whitespace=True):
|
||||
value = value[:128].strip()
|
||||
if not value:
|
||||
raise ValueError("Filename cannot be empty")
|
||||
if sys.version_info.major == 3:
|
||||
return value
|
||||
else:
|
||||
return value.decode('utf-8')
|
||||
return value
|
||||
|
||||
|
||||
def split_authors(values):
|
||||
@ -838,8 +833,8 @@ def get_download_link(book_id, book_format, client):
|
||||
ub.update_download(book_id, int(current_user.id))
|
||||
file_name = book.title
|
||||
if len(book.authors) > 0:
|
||||
file_name = book.authors[0].name + '_' + file_name
|
||||
file_name = get_valid_filename(file_name)
|
||||
file_name = file_name + ' - ' + book.authors[0].name
|
||||
file_name = get_valid_filename(file_name, replace_whitespace=False)
|
||||
headers = Headers()
|
||||
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||
headers["Content-Disposition"] = "attachment; filename=%s.%s; filename*=UTF-8''%s.%s" % (
|
||||
|
@ -41,7 +41,7 @@ class Google(Metadata):
|
||||
v['tags'] = r['volumeInfo'].get('categories', [])
|
||||
v['rating'] = r['volumeInfo'].get('averageRating', 0)
|
||||
if r['volumeInfo'].get('imageLinks'):
|
||||
v['cover'] = r['volumeInfo']['imageLinks']['thumbnail']
|
||||
v['cover'] = r['volumeInfo']['imageLinks']['thumbnail'].replace("http://", "https://")
|
||||
else:
|
||||
v['cover'] = "/../../../static/generic_cover.jpg"
|
||||
v['source'] = {
|
||||
|
@ -26,8 +26,10 @@ import inspect
|
||||
from flask import Blueprint, request, Response
|
||||
from flask_login import current_user
|
||||
from flask_login import login_required
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy.exc import OperationalError, InvalidRequestError
|
||||
|
||||
from . import constants, logger
|
||||
from . import constants, logger, ub
|
||||
from cps.services.Metadata import Metadata
|
||||
|
||||
meta = Blueprint('metadata', __name__)
|
||||
@ -63,17 +65,29 @@ def metadata_provider():
|
||||
active = current_user.view_settings.get('metadata', {})
|
||||
provider = list()
|
||||
for c in cl:
|
||||
provider.append({"name": c.__name__, "active": active.get(c.__name__, True), "id": c.__id__})
|
||||
provider.append({"name": c.__name__, "active": active.get(c.__id__, True), "id": c.__id__})
|
||||
return Response(json.dumps(provider), mimetype='application/json')
|
||||
|
||||
@meta.route("/metadata/provider", methods=['POST'])
|
||||
@login_required
|
||||
def metadata_change_active_provider():
|
||||
new_state = request.get_json()
|
||||
active = current_user.view_settings.get('metadata', {})
|
||||
active[new_state['id']] = new_state['value']
|
||||
current_user.view_settings['metadata'] = active
|
||||
try:
|
||||
try:
|
||||
flag_modified(current_user, "view_settings")
|
||||
except AttributeError:
|
||||
pass
|
||||
ub.session.commit()
|
||||
except (InvalidRequestError, OperationalError):
|
||||
log.error("Invalid request received: {}".format(request))
|
||||
return "Invalid request", 400
|
||||
provider = list()
|
||||
for c in cl:
|
||||
provider.append({"name": c.__name__, "active": active.get(c.__name__, True), "id": c.__id__})
|
||||
return Response(json.dumps(provider), mimetype='application/json')
|
||||
provider.append({"name": c.__name__, "active": active.get(c.__id__, True), "id": c.__id__})
|
||||
return "" # Response(json.dumps(provider), mimetype='application/json')
|
||||
|
||||
@meta.route("/metadata/search", methods=['POST'])
|
||||
@login_required
|
||||
@ -83,6 +97,6 @@ def metadata_search():
|
||||
active = current_user.view_settings.get('metadata', {})
|
||||
if query:
|
||||
for c in cl:
|
||||
if active.get(c.__name__, True):
|
||||
if active.get(c.__id__, True):
|
||||
data.extend(c.search(query))
|
||||
return Response(json.dumps(data), mimetype='application/json')
|
||||
|
@ -123,6 +123,10 @@ table .bg-dark-danger a { color: #fff; }
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row-fluid.text-center {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.container-fluid img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
@ -166,6 +170,10 @@ table .bg-dark-danger a { color: #fff; }
|
||||
box-shadow: 0 5px 8px -6px #777;
|
||||
}
|
||||
|
||||
.datepicker.form-control {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.container-fluid .book .cover span img {
|
||||
position: relative;
|
||||
top: 0;
|
||||
@ -322,7 +330,7 @@ table .bg-dark-danger:hover { background-color: #c9302c; }
|
||||
table .bg-primary:hover { background-color: #1c5484; }
|
||||
.block-label { display: block; }
|
||||
|
||||
.fake-input {
|
||||
.form-control.fake-input {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
|
@ -30,7 +30,7 @@ $("#have_read_cb").on("change", function() {
|
||||
$("#flash_danger").remove();
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
data.forEach(function (item) {
|
||||
$(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
$(".navbar").after('<div class="row-fluid text-center" >' +
|
||||
'<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' +
|
||||
'</div>');
|
||||
});
|
||||
|
@ -82,7 +82,6 @@ $(function () {
|
||||
success: function success(data) {
|
||||
// console.log(data);
|
||||
data.forEach(function(provider) {
|
||||
//$("#metadata_provider").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
|
||||
var checked = "";
|
||||
if (provider.active) {
|
||||
checked = "checked";
|
||||
@ -94,6 +93,17 @@ $(function () {
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("change", ".pill", function () {
|
||||
var id = $(this).data("control");
|
||||
var val = $(this).prop('checked');
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/metadata/provider",
|
||||
data: JSON.stringify({id : id, value: val}),
|
||||
});
|
||||
});
|
||||
|
||||
$("#meta-search").on("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
@ -172,7 +172,7 @@ $("#delete_confirm").click(function() {
|
||||
if (item.format != "") {
|
||||
$("button[data-delete-format='"+item.format+"']").addClass('hidden');
|
||||
}
|
||||
$( ".navbar" ).after( '<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
$( ".navbar" ).after( '<div class="row-fluid text-center" >' +
|
||||
'<div id="flash_'+item.type+'" class="alert alert-'+item.type+'">'+item.message+'</div>' +
|
||||
'</div>');
|
||||
|
||||
@ -543,7 +543,7 @@ $(function() {
|
||||
function handle_response(data) {
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
data.forEach(function (item) {
|
||||
$(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
$(".navbar").after('<div class="row-fluid text-center">' +
|
||||
'<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' +
|
||||
'</div>');
|
||||
});
|
||||
|
@ -744,7 +744,7 @@ function handleListServerResponse (data) {
|
||||
$("#flash_danger").remove();
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
data.forEach(function(item) {
|
||||
$(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
$(".navbar").after('<div class="row-fluid text-center">' +
|
||||
'<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' +
|
||||
'</div>');
|
||||
});
|
||||
|
@ -111,8 +111,8 @@
|
||||
{% endif %}
|
||||
<label for="pubdate">{{_('Published Date')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<input type="text" style="position: static;" class="datepicker form-control" name="pubdate" id="pubdate" value="{% if book.pubdate %}{{book.pubdate|formatdateinput}}{% endif %}">
|
||||
<input type="text" style="position: absolute;" class="form-control fake-input hidden" id="fake_pubdate" value="{% if book.pubdate %}{{book.pubdate|formatdate}}{% endif %}">
|
||||
<input type="text" class="datepicker form-control" name="pubdate" id="pubdate" value="{% if book.pubdate %}{{book.pubdate|formatdateinput}}{% endif %}">
|
||||
<input type="text" class="form-control fake-input hidden" id="fake_pubdate" value="{% if book.pubdate %}{{book.pubdate|formatdate}}{% endif %}">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="pubdate_delete" class="datepicker_delete btn btn-default"><span class="glyphicon glyphicon-remove-circle"></span></button>
|
||||
</span>
|
||||
@ -156,11 +156,11 @@
|
||||
|
||||
{% if c.datatype == 'datetime' %}
|
||||
<div class="input-group">
|
||||
<input type="text" style="position: static;" class="datepicker form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}"
|
||||
<input type="text" class="datepicker form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}"
|
||||
{% if book['custom_column_' ~ c.id]|length > 0 %}
|
||||
value="{% if book['custom_column_' ~ c.id][0].value %}{{ book['custom_column_' ~ c.id][0].value|formatdateinput}}{% endif %}"
|
||||
{% endif %}>
|
||||
<input type="text" style="position: absolute;" class="fake_custom_column_{{ c.id }} form-control fake-input hidden" id="fake_pubdate"
|
||||
<input type="text" class="fake_custom_column_{{ c.id }} form-control fake-input hidden" id="fake_pubdate_{{ c.id }}"
|
||||
{% if book['custom_column_' ~ c.id]|length > 0 %}
|
||||
value="{% if book['custom_column_' ~ c.id][0].value %}{{book['custom_column_' ~ c.id][0].value|formatdate}}{% endif %}"
|
||||
{% endif %}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block flash %}
|
||||
<div id="spinning_success" class="row-fluid text-center" style="margin-top: -20px; display:none;">
|
||||
<div id="spinning_success" class="row-fluid text-center" style="display:none;">
|
||||
<div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block flash %}
|
||||
<div id="spinning_success" class="row-fluid text-center" style="margin-top: -20px; display:none;">
|
||||
<div id="spinning_success" class="row-fluid text-center" style="display:none;">
|
||||
<div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -94,6 +94,10 @@
|
||||
</div>
|
||||
<div id="collapsefour" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_unicode_filename" name="config_unicode_filename" {% if config.config_unicode_filename %}checked{% endif %}>
|
||||
<label for="config_unicode_filename">{{_('Convert non-English characters in title and author while saving to disk')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_uploading" data-control="upload_settings" name="config_uploading" {% if config.config_uploading %}checked{% endif %}>
|
||||
<label for="config_uploading">{{_('Enable Uploads')}}</label>
|
||||
|
@ -91,22 +91,22 @@
|
||||
</div>
|
||||
{% for message in get_flashed_messages(with_categories=True) %}
|
||||
{%if message[0] == "error" %}
|
||||
<div class="row-fluid text-center" style="margin-top: -20px;">
|
||||
<div class="row-fluid text-center" >
|
||||
<div id="flash_danger" class="alert alert-danger">{{ message[1] }}</div>
|
||||
</div>
|
||||
{%endif%}
|
||||
{%if message[0] == "info" %}
|
||||
<div class="row-fluid text-center" style="margin-top: -20px;">
|
||||
<div class="row-fluid text-center">
|
||||
<div id="flash_info" class="alert alert-info">{{ message[1] }}</div>
|
||||
</div>
|
||||
{%endif%}
|
||||
{%if message[0] == "warning" %}
|
||||
<div class="row-fluid text-center" style="margin-top: -20px;">
|
||||
<div class="row-fluid text-center">
|
||||
<div id="flash_warning" class="alert alert-warning">{{ message[1] }}</div>
|
||||
</div>
|
||||
{%endif%}
|
||||
{%if message[0] == "success" %}
|
||||
<div class="row-fluid text-center" style="margin-top: -20px;">
|
||||
<div class="row-fluid text-center">
|
||||
<div id="flash_success" class="alert alert-success">{{ message[1] }}</div>
|
||||
</div>
|
||||
{%endif%}
|
||||
|
@ -19,8 +19,8 @@
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="publishstart">{{_('Published Date From')}}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" style="position: static;" class="datepicker form-control" name="publish_start" id="publishstart" value="">
|
||||
<input type="text" style="position: absolute;" class="form-control fake-input hidden" id="fake_publishstart" value="">
|
||||
<input type="text" class="datepicker form-control" name="publish_start" id="publishstart" value="">
|
||||
<input type="text" class="form-control fake-input hidden" id="fake_publishstart" value="">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="publishstart_delete" class="datepicker_delete btn btn-default"><span class="glyphicon glyphicon-remove-circle"></span></button>
|
||||
</span>
|
||||
@ -29,8 +29,8 @@
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="publishend">{{_('Published Date To')}}</label>
|
||||
<div class="input-group ">
|
||||
<input type="text" style="position: static;" class="datepicker form-control" name="publishend" id="publishend" value="">
|
||||
<input type="text" style="position: absolute;" class="form-control fake-input hidden" id="fake_publishend" value="">
|
||||
<input type="text" class="datepicker form-control" name="publishend" id="publishend" value="">
|
||||
<input type="text" class="form-control fake-input hidden" id="fake_publishend" value="">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="publishend_delete" class="datepicker_delete btn btn-default"><span class="glyphicon glyphicon-remove-circle"></span></button>
|
||||
</span>
|
||||
@ -178,8 +178,8 @@
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="{{ 'custom_column_' ~ c.id }}">{{_('From:')}}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" style="position: static;" class="datepicker form-control" name="{{ 'custom_column_' ~ c.id }}_start" id="{{ 'custom_column_' ~ c.id }}_start" value="">
|
||||
<input type="text" style="position: absolute;" class="form-control fake-input hidden" id="fake_{{ 'custom_column_' ~ c.id }}_start" value="">
|
||||
<input type="text" class="datepicker form-control" name="{{ 'custom_column_' ~ c.id }}_start" id="{{ 'custom_column_' ~ c.id }}_start" value="">
|
||||
<input type="text" class="form-control fake-input hidden" id="fake_{{ 'custom_column_' ~ c.id }}_start" value="">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="{{ 'custom_column_' ~ c.id }}_start_delete" class="datepicker_delete btn btn-default"><span class="glyphicon glyphicon-remove-circle"></span></button>
|
||||
</span>
|
||||
@ -188,8 +188,8 @@
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="{{ 'custom_column_' ~ c.id }}">{{_('To:')}}</label>
|
||||
<div class="input-group ">
|
||||
<input type="text" style="position: static;" class="datepicker form-control" name="{{ 'custom_column_' ~ c.id }}_end" id="{{ 'custom_column_' ~ c.id }}_end" value="">
|
||||
<input type="text" style="position: absolute;" class="form-control fake-input hidden" id="fake_{{ 'custom_column_' ~ c.id }}_end" value="">
|
||||
<input type="text" class="datepicker form-control" name="{{ 'custom_column_' ~ c.id }}_end" id="{{ 'custom_column_' ~ c.id }}_end" value="">
|
||||
<input type="text" class="form-control fake-input hidden" id="fake_{{ 'custom_column_' ~ c.id }}_end" value="">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="{{ 'custom_column_' ~ c.id }}_end_delete" class="datepicker_delete btn btn-default"><span class="glyphicon glyphicon-remove-circle"></span></button>
|
||||
</span>
|
||||
|
@ -178,7 +178,7 @@
|
||||
{{ restrict_modal() }}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script>
|
||||
|
@ -84,7 +84,7 @@ except ImportError:
|
||||
|
||||
@app.after_request
|
||||
def add_security_headers(resp):
|
||||
resp.headers['Content-Security-Policy']= "script-src 'self'"
|
||||
resp.headers['Content-Security-Policy']= "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src * data:"
|
||||
resp.headers['X-Content-Type-Options'] = 'nosniff'
|
||||
resp.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
resp.headers['X-XSS-Protection'] = '1; mode=block'
|
||||
|
Loading…
Reference in New Issue
Block a user