Added option to save books with no nenglish characters

Imporvements on metadata search
This commit is contained in:
Ozzie Isaacs 2021-08-01 13:50:17 +02:00
parent 702e96ddd6
commit ceec1051d5
17 changed files with 76 additions and 43 deletions

View File

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

View File

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

View File

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

View File

@ -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'] = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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