1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-28 12:30:00 +00:00

UI Improvements

Added additional restrictions to Calibre DB interface
This commit is contained in:
Ozzieisaacs 2020-05-03 10:55:33 +02:00
parent fcefd8031a
commit 0adcd1b3d9
10 changed files with 112 additions and 75 deletions

View File

@ -772,8 +772,7 @@ def new_user():
@admin_required
def edit_mailsettings():
content = config.get_mail_settings()
# log.debug("edit_mailsettings %r", content)
return render_title_template("email_edit.html", content=content, title=_(u"Edit e-mail server settings"),
return render_title_template("email_edit.html", content=content, title=_(u"Edit E-mail Server Settings"),
page="mailset")

View File

@ -44,6 +44,7 @@ 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>')
mail_size = Column(Integer, default=25)
config_calibre_dir = Column(String)
config_port = Column(Integer, default=constants.DEFAULT_PORT)

View File

@ -22,10 +22,11 @@ import sys
import os
import re
import ast
from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, ForeignKey
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float, DateTime
from sqlalchemy import Table, Column, ForeignKey, CheckConstraint
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float, DateTime, REAL
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
@ -72,9 +73,9 @@ class Identifiers(Base):
__tablename__ = 'identifiers'
id = Column(Integer, primary_key=True)
type = Column(String)
val = Column(String)
book = Column(Integer, ForeignKey('books.id'))
type = Column(String(collation='NOCASE'), nullable=False, default="isbn")
val = Column(String(collation='NOCASE'), nullable=False)
book = Column(Integer, ForeignKey('books.id'), nullable=False)
def __init__(self, val, id_type, book):
self.val = val
@ -126,8 +127,8 @@ class Comments(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
text = Column(String)
book = Column(Integer, ForeignKey('books.id'))
text = Column(String(collation='NOCASE'), nullable=False)
book = Column(Integer, ForeignKey('books.id'), nullable=False)
def __init__(self, text, book):
self.text = text
@ -141,7 +142,7 @@ class Tags(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
def __init__(self, name):
self.name = name
@ -154,9 +155,9 @@ class Authors(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
name = Column(String)
sort = Column(String)
link = Column(String)
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
sort = Column(String(collation='NOCASE'))
link = Column(String, nullable=False, default="")
def __init__(self, name, sort, link):
self.name = name
@ -171,8 +172,8 @@ class Series(Base):
__tablename__ = 'series'
id = Column(Integer, primary_key=True)
name = Column(String)
sort = Column(String)
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
sort = Column(String(collation='NOCASE'))
def __init__(self, name, sort):
self.name = name
@ -186,7 +187,7 @@ class Ratings(Base):
__tablename__ = 'ratings'
id = Column(Integer, primary_key=True)
rating = Column(Integer)
rating = Column(Integer, CheckConstraint('rating>-1 AND rating<11'), unique=True)
def __init__(self, rating):
self.rating = rating
@ -199,7 +200,7 @@ class Languages(Base):
__tablename__ = 'languages'
id = Column(Integer, primary_key=True)
lang_code = Column(String)
lang_code = Column(String(collation='NOCASE'), nullable=False, unique=True)
def __init__(self, lang_code):
self.lang_code = lang_code
@ -212,8 +213,8 @@ class Publishers(Base):
__tablename__ = 'publishers'
id = Column(Integer, primary_key=True)
name = Column(String)
sort = Column(String)
name = Column(String(collation='NOCASE'), nullable=False, unique=True)
sort = Column(String(collation='NOCASE'))
def __init__(self, name, sort):
self.name = name
@ -227,10 +228,10 @@ class Data(Base):
__tablename__ = 'data'
id = Column(Integer, primary_key=True)
book = Column(Integer, ForeignKey('books.id'))
format = Column(String)
uncompressed_size = Column(Integer)
name = Column(String)
book = Column(Integer, ForeignKey('books.id'), nullable=False)
format = Column(String(collation='NOCASE'), nullable=False)
uncompressed_size = Column(Integer, nullable=False)
name = Column(String, nullable=False)
def __init__(self, book, book_format, uncompressed_size, name):
self.book = book
@ -247,17 +248,20 @@ class Books(Base):
DEFAULT_PUBDATE = "0101-01-01 00:00:00+00:00"
id = Column(Integer, primary_key=True)
title = Column(String)
sort = Column(String)
author_sort = Column(String)
timestamp = Column(TIMESTAMP)
pubdate = Column(String)
series_index = Column(String)
last_modified = Column(TIMESTAMP)
path = Column(String)
has_cover = Column(Integer)
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(collation='NOCASE'), nullable=False, default='Unknown')
sort = Column(String(collation='NOCASE'))
author_sort = Column(String(collation='NOCASE'))
timestamp = Column(TIMESTAMP, default=datetime.utcnow)
pubdate = Column(TIMESTAMP, default=datetime.utcnow)
series_index = Column(REAL, nullable=False, default=1.0)
last_modified = Column(TIMESTAMP, default=datetime.utcnow)
path = Column(String, default="", nullable=False)
has_cover = Column(Integer, default=0)
uuid = Column(String)
isbn = Column(String(collation='NOCASE'), default="")
# Iccn = Column(String(collation='NOCASE'), default="")
flags = Column(Integer, nullable=False, default=1)
authors = relationship('Authors', secondary=books_authors_link, backref='books')
tags = relationship('Tags', secondary=books_tags_link, backref='books',order_by="Tags.name")

View File

@ -30,6 +30,7 @@ from uuid import uuid4
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
from flask_babel import gettext as _
from flask_login import current_user, login_required
from sqlalchemy import func
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
from . import config, get_locale, db, ub, worker
@ -680,10 +681,6 @@ def upload():
tags = meta.tags
series = meta.series
series_index = meta.series_id
title_dir = helper.get_valid_filename(title)
author_dir = helper.get_valid_filename(authr)
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
if title != _(u'Unknown') and authr != _(u'Unknown'):
entry = helper.check_exists_book(authr, title)
@ -692,6 +689,11 @@ def upload():
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
title_dir = helper.get_valid_filename(title)
author_dir = helper.get_valid_filename(authr)
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
if not os.path.exists(filepath):
try:
@ -721,7 +723,7 @@ def upload():
has_cover = 1
# handle authors
is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first()
is_author = db.session.query(db.Authors).filter(db.Authors.name == func.binary(authr)).first()
if is_author:
db_author = is_author
else:

View File

@ -14,10 +14,13 @@
<th>{{_('Send to Kindle E-mail Address')}}</th>
<th>{{_('Downloads')}}</th>
<th class="hidden-xs ">{{_('Admin')}}</th>
<th class="hidden-xs">{{_('Download')}}</th>
<th class="hidden-xs">{{_('View Books')}}</th>
<th class="hidden-xs">{{_('Upload')}}</th>
<th class="hidden-xs">{{_('Edit')}}</th>
<th class="hidden-xs hidden-sm">{{_('Password')}}</th>
<th class="hidden-xs hidden-sm">{{_('Upload')}}</th>
<th class="hidden-xs hidden-sm">{{_('Download')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('View Books')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('Edit')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('Delete')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('Public Shelf')}}</th>
</tr>
{% for user in allUser %}
{% if not user.role_anonymous() or config.config_anonbrowse %}
@ -27,10 +30,13 @@
<td>{{user.kindle_mail}}</td>
<td>{{user.downloads.count()}}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_admin()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_download()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_viewer()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_upload()) }}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_edit()) }}</td>
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_passwd()) }}</td>
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_upload()) }}</td>
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_download()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_viewer()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_delete_books()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit_shelfs()) }}</td>
</tr>
{% endif %}
{% endfor %}

View File

@ -3,7 +3,7 @@
<div class="discover">
<h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off">
<div class="panel-group">
<div class="panel-group col-md-10 col-lg-6">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
@ -15,9 +15,12 @@
</div>
<div id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body">
<div class="form-group required">
<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 class="form-group required input-group">
<label for="config_calibre_dir" class="sr-only">{{_('Location of Calibre Database')}}</label>
<input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
{% if feature_support['gdrive'] %}
<div class="form-group required">
@ -87,21 +90,25 @@
<label for="config_port">{{_('Server Port')}}</label>
<input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if config.config_port != None %}{{ config.config_port }}{% endif %}" autocomplete="off" required>
</div>
<div class="form-group">
<label for="config_certfile">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
<input type="text" class="form-control" name="config_certfile" id="config_certfile" value="{% if config.config_certfile != None %}{{ config.config_certfile }}{% endif %}" autocomplete="off">
<div class="form-group input-group">
<label for="config_certfile" class="sr-only">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
<input type="text" class="form-control" id="config_certfile" name="config_certfile" value="{% if config.config_certfile != None %}{{ config.config_certfile }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" id="certfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
<div class="form-group">
<label for="config_keyfile">{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
<input type="text" class="form-control" name="config_keyfile" id="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
<div class="form-group input-group">
<label for="config_calibre_dir" class="sr-only">{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
<input type="text" class="form-control" id="config_keyfile" name="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" id="keyfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
<div class="form-group">
<label for="config_updatechannel">{{_('Update Channel')}}</label>
<select name="config_updatechannel" id="config_updatechannel" class="form-control">
<option value="0" {% if config.config_updatechannel == 0 %}selected{% endif %}>{{_('Stable')}}</option>
<!--option value="1" {% if config.config_updatechannel == 1 %}selected{% endif %}>{{_('Stable (Automatic)')}}</option-->
<option value="2" {% if config.config_updatechannel == 2 %}selected{% endif %}>{{_('Nightly')}}</option>
<!--option-- value="3" {% if config.config_updatechannel == 3 %}selected{% endif %}>{{_('Nightly (Automatic)')}}</option-->
</select>
</div>
</div>
@ -342,18 +349,27 @@
<label for="config_calibre">{{_('Calibre E-Book Converter Settings')}}</label>
<input type="text" class="form-control" id="config_calibre" name="config_calibre" value="{% if config.config_calibre != None %}{{ config.config_calibre }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label>
<div class="form-group input-group">
<label for="config_converterpath" class="sr-only">{{_('Path to Calibre E-Book Converter')}}</label>
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if config.config_converterpath != None %}{{ config.config_converterpath }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
<div class="form-group">
<label for="config_calibre">{{_('Path to Kepubify E-Book Converter')}}</label>
<input type="text" class="form-control" id="config_kepubifypath" name="config_converterpath" value="{% if config.config_kepubifypath != None %}{{ config.config_kepubifypath }}{% endif %}" autocomplete="off">
<div class="form-group input-group">
<label for="config_kepubifypath" class="sr-only">{{_('Path to Kepubify E-Book Converter')}}</label>
<input type="text" class="form-control" id="config_kepubifypath" name="config_kepubifypath" value="{% if config.config_kepubifypath != None %}{{ config.config_kepubifypath }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" id="kepubify_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
{% if feature_support['rar'] %}
<div class="form-group">
<label for="config_rarfile_location">{{_('Location of Unrar binary')}}</label>
<input type="text" class="form-control" name="config_rarfile_location" id="config_rarfile_location" value="{% if config.config_rarfile_location != None %}{{ config.config_rarfile_location }}{% endif %}" autocomplete="off">
<div class="form-group input-group">
<label for="config_rarfile_location" class="sr-only">{{_('Location of Unrar binary')}}</label>
<input type="text" class="form-control" id="config_rarfile_location" name="config_rarfile_location" value="{% if config.config_rarfile_location != None %}{{ config.config_rarfile_location }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" id="unrar_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
{% endif %}
</div>

View File

@ -7,7 +7,7 @@
<div class="discover">
<h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off">
<div class="panel-group">
<div class="panel-group col-md-10 col-lg-6">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">

View File

@ -6,14 +6,14 @@
{% block body %}
<div class="discover">
<h1>{{title}}</h1>
<form role="form" method="POST">
<form role="form" class="col-md-10 col-lg-6" method="POST">
<div class="form-group">
<label for="mail_server">{{_('SMTP Hostname')}}</label>
<input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}">
</div>
<div class="form-group">
<label for="mail_port">{{_('SMTP Port')}}</label>
<input type="text" class="form-control" name="mail_port" id="mail_port" value="{{content.mail_port}}">
<input type="number" min="1" max="65535" class="form-control" name="mail_port" id="mail_port" value="{% if config.mail_port != None %}{{ config.mail_port }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="mail_use_ssl">{{_('Encryption')}}</label>
@ -34,6 +34,13 @@
<div class="form-group">
<label for="mail_from">{{_('From E-mail')}}</label>
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
</div>
<div class="form-group input-group">
<label for="mail_size" class="sr-only">{{_('Attachment Size Limit')}}</label>
<input type="number" min="1" max="600" class="form-control" name="attachment_size" id="mail_size" value="{% if config.mail_size != None %}{{ config.mail_size }}{% endif %}">
<span class="input-group-btn">
<button type="button" id="certfile_path" class="btn btn-default" disabled>MB</button>
</span>
</div>
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button>

View File

@ -1,6 +1,6 @@
{% extends "layout.html" %}
{% block body %}
<div class="col-sm-8">
<div class="col-md-10 col-lg-6">
<form role="form" id="search" action="{{ url_for('web.advanced_search') }}" method="GET">
<div class="form-group">
<label for="book_title">{{_('Book Title')}}</label>

View File

@ -3,6 +3,7 @@
<div class="discover">
<h1>{{title}}</h1>
<form role="form" method="POST" autocomplete="off">
<div class="col-md-10 col-lg-8">
{% if new_user or ( g.user and content.nickname != "Guest" and g.user.role_admin() ) %}
<div class="form-group required">
<label for="nickname">{{_('Username')}}</label>
@ -65,6 +66,7 @@
<div class="btn btn-danger" id="config_delete_kobo_token" data-toggle="modal" data-target="#modalDeleteToken" data-remote="false" {% if not content.remote_auth_token.first() %} style="display: none;" {% endif %}>{{_('Delete')}}</div>
</div>
{% endif %}
</div>
<div class="col-sm-6">
{% for element in sidebar %}
{% if element['config_show'] %}