mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-24 10:37:23 +00:00
Merge branch 'master' of https://github.com/janeczku/calibre-web
This commit is contained in:
commit
1ef2a96465
@ -20,16 +20,18 @@
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean, BLOB, JSON
|
||||
from sqlalchemy import Column, String, Integer, SmallInteger, Boolean, BLOB, JSON
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlalchemy.sql.expression import text
|
||||
try:
|
||||
# Compatibility with sqlalchemy 2.0
|
||||
from sqlalchemy.orm import declarative_base
|
||||
except ImportError:
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from . import constants, cli, logger, ub
|
||||
from . import constants, cli, logger
|
||||
|
||||
|
||||
log = logger.create()
|
||||
@ -260,7 +262,6 @@ class _ConfigSQL(object):
|
||||
"""
|
||||
new_value = dictionary.get(field, default)
|
||||
if new_value is None:
|
||||
# log.debug("_ConfigSQL set_from_dictionary field '%s' not found", field)
|
||||
return False
|
||||
|
||||
if field not in self.__dict__:
|
||||
@ -277,7 +278,6 @@ class _ConfigSQL(object):
|
||||
if current_value == new_value:
|
||||
return False
|
||||
|
||||
# log.debug("_ConfigSQL set_from_dictionary '%s' = %r (was %r)", field, new_value, current_value)
|
||||
setattr(self, field, new_value)
|
||||
return True
|
||||
|
||||
@ -368,20 +368,23 @@ def _migrate_table(session, orm_class):
|
||||
column_default = ""
|
||||
else:
|
||||
if isinstance(column.default.arg, bool):
|
||||
column_default = ("DEFAULT %r" % int(column.default.arg))
|
||||
column_default = "DEFAULT {}".format(int(column.default.arg))
|
||||
else:
|
||||
column_default = ("DEFAULT '%r'" % column.default.arg)
|
||||
column_default = "DEFAULT `{}`".format(column.default.arg)
|
||||
if isinstance(column.type, JSON):
|
||||
column_type = "JSON"
|
||||
else:
|
||||
column_type = column.type
|
||||
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
|
||||
alter_table = text("ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
|
||||
column_name,
|
||||
column_type,
|
||||
column_default)
|
||||
column_default))
|
||||
log.debug(alter_table)
|
||||
session.execute(alter_table)
|
||||
changed = True
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
log.error("Database corrupt column: {}".format(column_name))
|
||||
log.debug(e)
|
||||
|
||||
if changed:
|
||||
try:
|
||||
|
19
cps/db.py
19
cps/db.py
@ -121,6 +121,8 @@ class Identifiers(Base):
|
||||
return u"Douban"
|
||||
elif format_type == "goodreads":
|
||||
return u"Goodreads"
|
||||
elif format_type == "babelio":
|
||||
return u"Babelio"
|
||||
elif format_type == "google":
|
||||
return u"Google Books"
|
||||
elif format_type == "kobo":
|
||||
@ -148,6 +150,8 @@ class Identifiers(Base):
|
||||
return u"https://dx.doi.org/{0}".format(self.val)
|
||||
elif format_type == "goodreads":
|
||||
return u"https://www.goodreads.com/book/show/{0}".format(self.val)
|
||||
elif format_type == "babelio":
|
||||
return u"https://www.babelio.com/livres/titre/{0}".format(self.val)
|
||||
elif format_type == "douban":
|
||||
return u"https://book.douban.com/subject/{0}".format(self.val)
|
||||
elif format_type == "google":
|
||||
@ -702,14 +706,21 @@ class CalibreDB():
|
||||
return self.session.query(Books) \
|
||||
.filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first()
|
||||
|
||||
def search_query(self, term):
|
||||
def search_query(self, term, *join):
|
||||
term.strip().lower()
|
||||
self.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
q = list()
|
||||
authorterms = re.split("[, ]+", term)
|
||||
for authorterm in authorterms:
|
||||
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
|
||||
return self.session.query(Books).filter(self.common_filters(True)).filter(
|
||||
query = self.session.query(Books)
|
||||
if len(join) == 3:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
||||
elif len(join) == 2:
|
||||
query = query.outerjoin(join[0], join[1])
|
||||
elif len(join) == 1:
|
||||
query = query.outerjoin(join[0])
|
||||
return query.filter(self.common_filters(True)).filter(
|
||||
or_(Books.tags.any(func.lower(Tags.name).ilike("%" + term + "%")),
|
||||
Books.series.any(func.lower(Series.name).ilike("%" + term + "%")),
|
||||
Books.authors.any(and_(*q)),
|
||||
@ -718,10 +729,10 @@ class CalibreDB():
|
||||
))
|
||||
|
||||
# read search results from calibre-database and return it (function is used for feed and simple search
|
||||
def get_search_results(self, term, offset=None, order=None, limit=None):
|
||||
def get_search_results(self, term, offset=None, order=None, limit=None, *join):
|
||||
order = order or [Books.sort]
|
||||
pagination = None
|
||||
result = self.search_query(term).order_by(*order).all()
|
||||
result = self.search_query(term, *join).order_by(*order).all()
|
||||
result_count = len(result)
|
||||
if offset != None and limit != None:
|
||||
offset = int(offset)
|
||||
|
@ -49,5 +49,5 @@ except ImportError as err:
|
||||
try:
|
||||
from . import gmail
|
||||
except ImportError as err:
|
||||
log.debug("Cannot import Gmail, sending books via G-Mail Accounts will not work: %s", err)
|
||||
log.debug("Cannot import gmail, sending books via Gmail Oauth2 Verification will not work: %s", err)
|
||||
gmail = None
|
||||
|
@ -53,6 +53,7 @@ def setup_gmail(token):
|
||||
'expiry': creds.expiry.isoformat(),
|
||||
'email': user_info
|
||||
}
|
||||
return {}
|
||||
|
||||
def get_user_info(credentials):
|
||||
user_info_service = build(serviceName='oauth2', version='v2',credentials=credentials)
|
||||
|
@ -84,15 +84,24 @@ body {
|
||||
#progress .bar-load,
|
||||
#progress .bar-read {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
transition: width 150ms ease-in-out;
|
||||
}
|
||||
|
||||
#progress .from-left {
|
||||
left: 0;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#progress .from-right {
|
||||
right: 0;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
#progress .bar-load {
|
||||
color: #000;
|
||||
background-color: #ccc;
|
||||
|
@ -171,7 +171,10 @@ kthoom.ImageFile = function(file) {
|
||||
|
||||
function initProgressClick() {
|
||||
$("#progress").click(function(e) {
|
||||
var page = Math.max(1, Math.ceil((e.offsetX / $(this).width()) * totalImages)) - 1;
|
||||
var offset = $(this).offset();
|
||||
var x = e.pageX - offset.left;
|
||||
var rate = settings.direction === 0 ? x / $(this).width() : 1 - x / $(this).width();
|
||||
var page = Math.max(1, Math.ceil(rate * totalImages)) - 1;
|
||||
currentImage = page;
|
||||
updatePage();
|
||||
});
|
||||
@ -285,6 +288,22 @@ function updatePage() {
|
||||
}
|
||||
|
||||
function updateProgress(loadPercentage) {
|
||||
if (settings.direction === 0) {
|
||||
$("#progress .bar-read")
|
||||
.removeClass("from-right")
|
||||
.addClass("from-left");
|
||||
$("#progress .bar-load")
|
||||
.removeClass("from-right")
|
||||
.addClass("from-left");
|
||||
} else {
|
||||
$("#progress .bar-read")
|
||||
.removeClass("from-left")
|
||||
.addClass("from-right");
|
||||
$("#progress .bar-load")
|
||||
.removeClass("from-left")
|
||||
.addClass("from-right");
|
||||
}
|
||||
|
||||
// Set the load/unzip progress if it's passed in
|
||||
if (loadPercentage) {
|
||||
$("#progress .bar-load").css({ width: loadPercentage + "%" });
|
||||
@ -526,18 +545,17 @@ function keyHandler(evt) {
|
||||
break;
|
||||
case kthoom.Key.SPACE:
|
||||
var container = $("#mainContent");
|
||||
var atTop = container.scrollTop() === 0;
|
||||
var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
|
||||
// var atTop = container.scrollTop() === 0;
|
||||
// var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
|
||||
|
||||
if (evt.shiftKey && atTop) {
|
||||
if (evt.shiftKey) {
|
||||
evt.preventDefault();
|
||||
// If it's Shift + Space and the container is at the top of the page
|
||||
showLeftPage();
|
||||
} else if (!evt.shiftKey && atBottom) {
|
||||
showPrevPage();
|
||||
} else {
|
||||
evt.preventDefault();
|
||||
// If you're at the bottom of the page and you only pressed space
|
||||
showRightPage();
|
||||
container.scrollTop(0);
|
||||
showNextPage();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -114,6 +114,20 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
$(".session").click(function() {
|
||||
window.sessionStorage.setItem("back", window.location.pathname);
|
||||
});
|
||||
|
||||
$("#back").click(function() {
|
||||
var loc = sessionStorage.getItem("back");
|
||||
if (!loc) {
|
||||
loc = $(this).data("back");
|
||||
}
|
||||
sessionStorage.removeItem("back");
|
||||
window.location.href = loc;
|
||||
|
||||
});
|
||||
|
||||
function confirmDialog(id, dialogid, dataValue, yesFn, noFn) {
|
||||
var $confirm = $("#" + dialogid);
|
||||
$confirm.modal('show');
|
||||
|
@ -177,7 +177,6 @@ $(function() {
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
$("#domain_allow_submit").click(function(event) {
|
||||
event.preventDefault();
|
||||
$("#domain_add_allow").ajaxForm();
|
||||
@ -388,7 +387,6 @@ $(function() {
|
||||
var target = $(e.relatedTarget).attr('id');
|
||||
var dataId;
|
||||
$(e.relatedTarget).one('focus', function(e){$(this).blur();});
|
||||
//$(e.relatedTarget).blur();
|
||||
if ($(e.relatedTarget).hasClass("button_head")) {
|
||||
dataId = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
} else {
|
||||
@ -454,18 +452,28 @@ $(function() {
|
||||
$(this).next().text(elText);
|
||||
});
|
||||
},
|
||||
onLoadSuccess: function () {
|
||||
var guest = $(".editable[data-name='name'][data-value='Guest']");
|
||||
guest.editable("disable");
|
||||
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").editable("disable");
|
||||
$("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$(".user-remove[data-pk='"+guest.data("pk")+"']").hide();
|
||||
onPostHeader () {
|
||||
deactivateHeaderButtons();
|
||||
},
|
||||
onSort: function(a, b) {
|
||||
console.log("huh");
|
||||
onLoadSuccess: function () {
|
||||
loadSuccess();
|
||||
var element = $(".header_select");
|
||||
element.each(function() {
|
||||
var item = $(this).parent();
|
||||
var parent = item.parent().parent();
|
||||
if (parent.prop('nodeName') === "TH") {
|
||||
item.prependTo(parent);
|
||||
}
|
||||
});
|
||||
var element = $(".form-check");
|
||||
element.each(function() {
|
||||
var item = $(this).parent();
|
||||
var parent = item.parent().parent();
|
||||
if (parent.prop('nodeName') === "TH") {
|
||||
item.prependTo(parent);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
onColumnSwitch: function () {
|
||||
var visible = $("#user-table").bootstrapTable("getVisibleColumns");
|
||||
@ -491,7 +499,18 @@ $(function() {
|
||||
$("#user_delete_selection").click(function() {
|
||||
$("#user-table").bootstrapTable("uncheckAll");
|
||||
});
|
||||
|
||||
$("#select_locale").on("change",function() {
|
||||
selectHeader(this, "locale");
|
||||
});
|
||||
$("#select_default_language").on("change",function() {
|
||||
selectHeader(this, "default_language");
|
||||
});
|
||||
$(".check_head").on("change",function() {
|
||||
var val = $(this).val() === "on";
|
||||
var name = $(this).data("name");
|
||||
var data = $(this).data("val");
|
||||
checkboxHeader(val, name, data);
|
||||
});
|
||||
function user_handle (userId) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
@ -505,6 +524,7 @@ $(function() {
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
loadSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -560,10 +580,6 @@ function TableActions (value, row) {
|
||||
].join("");
|
||||
}
|
||||
|
||||
function editEntry(param)
|
||||
{
|
||||
console.log(param);
|
||||
}
|
||||
/* Function for deleting domain restrictions */
|
||||
function RestrictionActions (value, row) {
|
||||
return [
|
||||
@ -582,7 +598,7 @@ function EbookActions (value, row) {
|
||||
].join("");
|
||||
}
|
||||
|
||||
/* Function for deleting books */
|
||||
/* Function for deleting Users */
|
||||
function UserActions (value, row) {
|
||||
return [
|
||||
"<div class=\"user-remove\" data-value=\"delete\" onclick=\"deleteUser(this, '" + row.id + "')\" data-pk=\"" + row.id + "\" title=\"Remove\">",
|
||||
@ -600,7 +616,7 @@ function responseHandler(res) {
|
||||
}
|
||||
|
||||
function singleUserFormatter(value, row) {
|
||||
return '<a class="btn btn-default" href="' + window.location.pathname + '/../../admin/user/' + row.id + '">' + this.buttontext + '</a>'
|
||||
return '<a class="btn btn-default" onclick="storeLocation()" href="' + window.location.pathname + '/../../admin/user/' + row.id + '">' + this.buttontext + '</a>'
|
||||
}
|
||||
|
||||
function checkboxFormatter(value, row, index){
|
||||
@ -610,15 +626,22 @@ function checkboxFormatter(value, row, index){
|
||||
return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', ' + this.column + ')">';
|
||||
}
|
||||
|
||||
function checkboxChange(checkbox, userId, field, field_index) {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": userId, "field_index": field_index, "value": checkbox.checked},
|
||||
success: function (data) {
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
function loadSuccess() {
|
||||
var guest = $(".editable[data-name='name'][data-value='Guest']");
|
||||
guest.editable("disable");
|
||||
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").editable("disable");
|
||||
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").hide();
|
||||
$("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$(".user-remove[data-pk='"+guest.data("pk")+"']").hide();
|
||||
}
|
||||
|
||||
function handleListServerResponse (data, disableButtons) {
|
||||
$("#flash_success").remove();
|
||||
$("#flash_danger").remove();
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
$( ".navbar" ).after( '<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
'<div id="flash_'+data.type+'" class="alert alert-'+data.type+'">'+data.message+'</div>' +
|
||||
'</div>');
|
||||
@ -630,13 +653,28 @@ function checkboxChange(checkbox, userId, field, field_index) {
|
||||
timeout: 900,
|
||||
success: function (data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
if (disableButtons) {
|
||||
deactivateHeaderButtons();
|
||||
}
|
||||
});
|
||||
loadSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deactivateHeaderButtons(e) {
|
||||
|
||||
function checkboxChange(checkbox, userId, field, field_index) {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": userId, "field_index": field_index, "value": checkbox.checked},
|
||||
error: function(data) {
|
||||
handleListServerResponse({type:"danger", message:data.responseText})
|
||||
},
|
||||
success: handleListServerResponse
|
||||
});
|
||||
}
|
||||
|
||||
function deactivateHeaderButtons() {
|
||||
$("#user_delete_selection").addClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", true);
|
||||
$(".check_head").attr("aria-disabled", true);
|
||||
@ -655,18 +693,10 @@ function selectHeader(element, field) {
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "value": element.value},
|
||||
success: function () {
|
||||
$.ajax({
|
||||
method: "get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success: function (data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
deactivateHeaderButtons();
|
||||
}
|
||||
});
|
||||
}
|
||||
error: function (data) {
|
||||
handleListServerResponse({type:"danger", message:data.responseText})
|
||||
},
|
||||
success: handleListServerResponse,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -679,25 +709,12 @@ function checkboxHeader(CheckboxState, field, field_index) {
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "field_index": field_index, "value": CheckboxState},
|
||||
success: function () {
|
||||
$.ajax({
|
||||
method: "get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
error: function (data) {
|
||||
handleListServerResponse({type:"danger", message:data.responseText}, true)
|
||||
},
|
||||
success: function (data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
$("#user_delete_selection").addClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", true);
|
||||
$(".check_head").attr("aria-disabled", true);
|
||||
$(".check_head").attr("disabled", true);
|
||||
$(".check_head").prop('checked', false);
|
||||
$(".button_head").attr("aria-disabled", true);
|
||||
$(".button_head").addClass("disabled");
|
||||
$(".header_select").attr("disabled", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
handleListServerResponse (data, true)
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -712,24 +729,10 @@ function deleteUser(a,b){
|
||||
method:"post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid":b},
|
||||
success:function(data) {
|
||||
$("#flash_success").remove();
|
||||
$("#flash_danger").remove();
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
$( ".navbar" ).after( '<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
'<div id="flash_'+data.type+'" class="alert alert-'+data.type+'">'+data.message+'</div>' +
|
||||
'</div>');
|
||||
}
|
||||
$.ajax({
|
||||
method: "get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success: function (data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
}
|
||||
});
|
||||
}
|
||||
success: handleListServerResponse,
|
||||
error: function (data) {
|
||||
handleListServerResponse({type:"danger", message:data.responseText})
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -741,6 +744,6 @@ function queryParams(params)
|
||||
return params;
|
||||
}
|
||||
|
||||
function test(){
|
||||
console.log("hello");
|
||||
function storeLocation() {
|
||||
window.sessionStorage.setItem("back", window.location.pathname);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
{% for user in allUser %}
|
||||
{% if not user.role_anonymous() or config.config_anonbrowse %}
|
||||
<tr>
|
||||
<td><a href="{{url_for('admin.edit_user', user_id=user.id)}}">{{user.name}}</a></td>
|
||||
<td><a class="session" href="{{url_for('admin.edit_user', user_id=user.id)}}">{{user.name}}</a></td>
|
||||
<td>{{user.email}}</td>
|
||||
<td>{{user.kindle_mail}}</td>
|
||||
<td>{{user.downloads.count()}}</td>
|
||||
|
@ -47,13 +47,13 @@
|
||||
{{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }}
|
||||
{{ text_table_row('sort', _('Enter Title Sort'),_('Title Sort'), false, true) }}
|
||||
{{ text_table_row('author_sort', _('Enter Author Sort'),_('Author Sort'), false, true) }}
|
||||
{{ text_table_row('authors', _('Enter Authors'),_('Authors'), true) }}
|
||||
{{ text_table_row('tags', _('Enter Categories'),_('Categories'), false, false) }}
|
||||
{{ text_table_row('series', _('Enter Series'),_('Series'), false, false) }}
|
||||
{{ text_table_row('authors', _('Enter Authors'),_('Authors'), true, true) }}
|
||||
{{ text_table_row('tags', _('Enter Categories'),_('Categories'), false, true) }}
|
||||
{{ text_table_row('series', _('Enter Series'),_('Series'), false, true) }}
|
||||
<th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('editbook.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter title')}}"{% endif %}>{{_('Series Index')}}</th>
|
||||
{{ text_table_row('languages', _('Enter Languages'),_('Languages'), false, false) }}
|
||||
{{ text_table_row('languages', _('Enter Languages'),_('Languages'), false, true) }}
|
||||
<!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th-->
|
||||
{{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false, false) }}
|
||||
{{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false, true) }}
|
||||
{% if g.user.role_delete_books() and g.user.role_edit()%}
|
||||
<th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th>
|
||||
{% endif %}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<label for="mail_server_type">{{_('Choose Server Type')}}</label>
|
||||
<select name="mail_server_type" id="config_email_type" class="form-control" data-control="email-settings">
|
||||
<option value="0" {% if content.mail_server_type == 0 %}selected{% endif %}>{{_('Use Standard E-Mail Account')}}</option>
|
||||
<option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('G-Mail Account with OAuth2 Verfification')}}</option>
|
||||
<option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('Gmail Account with OAuth2 Verfification')}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-related="email-settings-1">
|
||||
@ -20,7 +20,7 @@
|
||||
{% if content.mail_gmail_token == {} %}
|
||||
<button type="submit" id="gmail_server" name="gmail" value="submit" class="btn btn-default">{{_('Setup Gmail Account as E-Mail Server')}}</button>
|
||||
{% else %}
|
||||
<button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke G-Mail Access')}}</button>
|
||||
<button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke Gmail Access')}}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -66,7 +66,7 @@
|
||||
{% if feature_support['gmail'] %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Back')}}</a>
|
||||
<a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Back')}}</a>
|
||||
</form>
|
||||
{% if g.allow_registration %}
|
||||
<div class="col-md-10 col-lg-6">
|
||||
|
@ -60,12 +60,12 @@
|
||||
<a id="fullscreen" class="icon-resize-full">Fullscreen</a>
|
||||
</div>
|
||||
<div id="progress" role="progressbar" class="loading">
|
||||
<div class="bar-load">
|
||||
<div class="bar-load from-left">
|
||||
<div class="text load">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar-read">
|
||||
<div class="bar-read from-left">
|
||||
<div class="text page"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -129,7 +129,7 @@
|
||||
<div class="col-sm-12">
|
||||
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
|
||||
{% if not profile %}
|
||||
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a>
|
||||
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
|
||||
{% endif %}
|
||||
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
|
||||
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
|
||||
|
@ -24,15 +24,17 @@
|
||||
data-column="{{value.get(array_field)}}"
|
||||
data-formatter="checkboxFormatter">
|
||||
<div class="form-check">
|
||||
<div>
|
||||
<label>
|
||||
<input type="radio" class="check_head" name="options_{{array_field}}" onchange="checkboxHeader('false', '{{parameter}}', {{value.get(array_field)}})" disabled>{{_('Deny')}}
|
||||
<input type="radio" class="check_head" data-val={{value.get(array_field)}} name="options_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Deny')}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<div>
|
||||
<label>
|
||||
<input type="radio" class="check_head" name="options_{{array_field}}" onchange="checkboxHeader('true', '{{parameter}}', {{value.get(array_field)}})" disabled>{{_('Allow')}}
|
||||
<input type="radio" class="check_head" data-val={{value.get(array_field)}} name="options_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Allow')}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{show_text}}
|
||||
</th>
|
||||
{%- endmacro %}
|
||||
@ -48,14 +50,13 @@
|
||||
data-editable-source={{url}}
|
||||
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
|
||||
<div>
|
||||
<select id="select_{{ parameter }}" class="header_select" onchange="selectHeader(this, '{{parameter}}')" disabled="">
|
||||
<select id="select_{{ parameter }}" class="header_select" disabled="">
|
||||
<option value="all">{{ _('Show All') }}</option>
|
||||
{% for language in languages %}
|
||||
<option value="{{language.lang_code}}">{{language.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div><br>
|
||||
|
||||
</div>
|
||||
{{ show_text }}
|
||||
</th>
|
||||
{%- endmacro %}
|
||||
@ -71,13 +72,13 @@
|
||||
data-editable-source={{url}}
|
||||
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
|
||||
<div>
|
||||
<select id="select_{{ parameter }}" class="header_select" onchange="selectHeader(this, '{{parameter}}')" disabled="">
|
||||
<select id="select_{{ parameter }}" class="header_select" disabled="">
|
||||
<option value="None">{{_('Select...')}}</option>
|
||||
{% for translation in translations %}
|
||||
<option value="{{translation}}">{{translation.display_name|capitalize}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div><br>
|
||||
</div>
|
||||
{{ show_text }}
|
||||
</th>
|
||||
{%- endmacro %}
|
||||
@ -137,6 +138,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="errorlink">
|
||||
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Back')}}</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block modal %}
|
||||
{{ delete_confirm_modal() }}
|
||||
|
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
@ -75,8 +75,9 @@ def load_user_from_auth_header(header_val):
|
||||
basic_username = basic_password = '' # nosec
|
||||
try:
|
||||
header_val = base64.b64decode(header_val).decode('utf-8')
|
||||
basic_username = header_val.split(':')[0]
|
||||
basic_password = header_val.split(':')[1]
|
||||
# Users with colon are invalid: rfc7617 page 4
|
||||
basic_username = header_val.split(':', 1)[0]
|
||||
basic_password = header_val.split(':', 1)[1]
|
||||
except (TypeError, UnicodeDecodeError, binascii.Error):
|
||||
pass
|
||||
user = _fetch_user_by_name(basic_username)
|
||||
|
22
cps/web.py
22
cps/web.py
@ -760,10 +760,26 @@ def list_books():
|
||||
sort = request.args.get("sort", "id")
|
||||
order = request.args.get("order", "").lower()
|
||||
state = None
|
||||
join = tuple()
|
||||
|
||||
if sort == "state":
|
||||
state = json.loads(request.args.get("state", "[]"))
|
||||
if sort != "state" and order:
|
||||
elif sort == "tags":
|
||||
order = [db.Tags.name.asc()] if order == "asc" else [db.Tags.name.desc()]
|
||||
join = db.books_tags_link,db.Books.id == db.books_tags_link.c.book, db.Tags
|
||||
elif sort == "series":
|
||||
order = [db.Series.name.asc()] if order == "asc" else [db.Series.name.desc()]
|
||||
join = db.books_series_link,db.Books.id == db.books_series_link.c.book, db.Series
|
||||
elif sort == "publishers":
|
||||
order = [db.Publishers.name.asc()] if order == "asc" else [db.Publishers.name.desc()]
|
||||
join = db.books_publishers_link,db.Books.id == db.books_publishers_link.c.book, db.Publishers
|
||||
elif sort == "authors":
|
||||
order = [db.Authors.name.asc()] if order == "asc" else [db.Authors.name.desc()]
|
||||
join = db.books_authors_link,db.Books.id == db.books_authors_link.c.book, db.Authors
|
||||
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
|
||||
elif order and sort in ["sort", "title", "authors_sort", "series_index"]:
|
||||
order = [text(sort + " " + order)]
|
||||
elif not state:
|
||||
order = [db.Books.timestamp.desc()]
|
||||
@ -778,9 +794,9 @@ def list_books():
|
||||
books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all()
|
||||
entries = calibre_db.get_checkbox_sorted(books, state, off, limit,order)
|
||||
elif search:
|
||||
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit)
|
||||
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit, *join)
|
||||
else:
|
||||
entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order)
|
||||
entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order, *join)
|
||||
|
||||
for entry in entries:
|
||||
for index in range(0, len(entry.languages)):
|
||||
|
539
messages.pot
539
messages.pot
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user