Anonymous account now can also be configured like normal user (e.g. download permissions, change visibility of sidebar) (#35)

Search now working for calibre-companion (#79), download not working yet
metadata view 80% finished
This commit is contained in:
OzzieIsaacs 2017-01-12 20:43:36 +01:00
parent beca61a596
commit 0107c52b66
7 changed files with 133 additions and 100 deletions

View File

@ -13,9 +13,9 @@
<th>{{_('Upload')}}</th> <th>{{_('Upload')}}</th>
<th>{{_('Edit')}}</th> <th>{{_('Edit')}}</th>
<th>{{_('Passwd')}}</th> <th>{{_('Passwd')}}</th>
</tr> </tr>
{% for user in content %} {% for user in content %}
{% if not user.role_anonymous() or config.ANON_BROWSE %}
<tr> <tr>
<td><a href="{{url_for('edit_user', user_id=user.id)}}">{{user.nickname}}</a></td> <td><a href="{{url_for('edit_user', user_id=user.id)}}">{{user.nickname}}</a></td>
<td>{{user.email}}</td> <td>{{user.email}}</td>
@ -26,7 +26,8 @@
<td>{% if user.role_upload() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if user.role_upload() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if user.role_edit() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if user.role_edit() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if user.role_passwd() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if user.role_passwd() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
</tr>
{% endif %}
{% endfor %} {% endfor %}
</table> </table>
<div class="btn btn-default"><a href="{{url_for('new_user')}}">{{_('Add new user')}}</a></div> <div class="btn btn-default"><a href="{{url_for('new_user')}}">{{_('Add new user')}}</a></div>

View File

@ -104,11 +104,11 @@
{{entry.comments[0].text|safe}} {{entry.comments[0].text|safe}}
{% endif %} {% endif %}
{% if g.user.is_authenticated %}
<div class="more-stuff"> <div class="more-stuff">
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Download, send to Kindle, reading"> <div class="btn-group" role="group" aria-label="Download, send to Kindle, reading">
{% if g.user.role_download() %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-download"></span> {{_('Download')}} <span class="glyphicon glyphicon-download"></span> {{_('Download')}}
@ -120,6 +120,9 @@
{%endfor%} {%endfor%}
</ul> </ul>
</div> </div>
{% endif %}
{% if g.user.is_authenticated %}
{% if g.user.kindle_mail %} {% if g.user.kindle_mail %}
<a href="{{url_for('send_to_kindle', book_id=entry.id)}}" id="sendbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-send"></span> {{_('Send to Kindle')}}</a> <a href="{{url_for('send_to_kindle', book_id=entry.id)}}" id="sendbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-send"></span> {{_('Send to Kindle')}}</a>
{% endif %} {% endif %}
@ -136,10 +139,11 @@
{%endfor%} {%endfor%}
</ul> </ul>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</br> </br>
{% if g.user.is_authenticated %}
{% if g.user.shelf.all() or g.public_shelfes %} {% if g.user.shelf.all() or g.public_shelfes %}
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Add to shelves"> <div class="btn-group" role="group" aria-label="Add to shelves">
@ -180,7 +184,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endif %}
{% if g.user.role_edit() %} {% if g.user.role_edit() %}
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Edit/Delete book"> <div class="btn-group" role="group" aria-label="Edit/Delete book">
@ -189,7 +193,7 @@
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,12 +10,11 @@
<link rel="up" <link rel="up"
href="{{url_for('feed_index')}}" href="{{url_for('feed_index')}}"
type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
{% if pagination.has_prev %}
<link rel="first" <link rel="first"
href="{{request.script_root + request.path}}" href="{{request.script_root + request.path}}"
type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<link rel="last" {% endif %}
href="{{request.script_root + request.path}}?offset={{ pagination.last_offset }}"
type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
{% if pagination.has_next %} {% if pagination.has_next %}
<link rel="next" <link rel="next"
title="{{_('Next')}}" title="{{_('Next')}}"

View File

@ -146,10 +146,11 @@
<li><a href="{{url_for('show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list"></span> {{shelf.name}}</a></li> <li><a href="{{url_for('show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list"></span> {{shelf.name}}</a></li>
{% endfor %} {% endfor %}
{% if not g.user.is_anonymous() %} {% if not g.user.is_anonymous() %}
<li class="create-shelf"><a href="{{url_for('create_shelf')}}">{{_('Create a Shelf')}}</a></li> <li id="nav_createshelf" class="create-shelf"><a href="{{url_for('create_shelf')}}">{{_('Create a Shelf')}}</a></li>
<li id="nav_about"><a href="{{url_for('stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
<li><a href="{{url_for('stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li>
</ul> </ul>
</nav> </nav>
</div> </div>

View File

@ -13,7 +13,7 @@
<label for="email">{{_('Email address')}}</label> <label for="email">{{_('Email address')}}</label>
<input type="email" class="form-control" name="email" id="email" value="{{ content.email if content.email != None }}" autocomplete="off" required> <input type="email" class="form-control" name="email" id="email" value="{{ content.email if content.email != None }}" autocomplete="off" required>
</div> </div>
{% if g.user and g.user.role_passwd() or g.user.role_admin()%} {% if ( g.user and g.user.role_passwd() or g.user.role_admin() ) and not content.role_anonymous() %}
<div class="form-group"> <div class="form-group">
<label for="password">{{_('Password')}}</label> <label for="password">{{_('Password')}}</label>
<input type="password" class="form-control" name="password" id="password" value="" autocomplete="off"> <input type="password" class="form-control" name="password" id="password" value="" autocomplete="off">
@ -23,6 +23,7 @@
<label for="kindle_mail">{{_('Kindle E-Mail')}}</label> <label for="kindle_mail">{{_('Kindle E-Mail')}}</label>
<input type="email" class="form-control" name="kindle_mail" id="kindle_mail" value="{{ content.kindle_mail if content.kindle_mail != None }}"> <input type="email" class="form-control" name="kindle_mail" id="kindle_mail" value="{{ content.kindle_mail if content.kindle_mail != None }}">
</div> </div>
{% if not content.role_anonymous() %}
<div class="form-group"> <div class="form-group">
<label for="locale">{{_('Language')}}</label> <label for="locale">{{_('Language')}}</label>
<select name="locale" id="locale" class="form-control"> <select name="locale" id="locale" class="form-control">
@ -31,6 +32,7 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
{% endif %}
<div class="form-group"> <div class="form-group">
<label for="default_language">{{_('Show books with language')}}</label> <label for="default_language">{{_('Show books with language')}}</label>
<select name="default_language" id="default_language" class="form-control"> <select name="default_language" id="default_language" class="form-control">
@ -62,11 +64,12 @@
</div> </div>
{% if g.user and g.user.role_admin() and not profile %} {% if g.user and g.user.role_admin() and not profile %}
{% if not content.role_anonymous() %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}> <input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}>
<label for="admin_role">{{_('Admin user')}}</label> <label for="admin_role">{{_('Admin user')}}</label>
</div> </div>
{% endif %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="download_role" id="download_role" {% if content.role_download() %}checked{% endif %}> <input type="checkbox" name="download_role" id="download_role" {% if content.role_download() %}checked{% endif %}>
<label for="download_role">{{_('Allow Downloads')}}</label> <label for="download_role">{{_('Allow Downloads')}}</label>
@ -79,19 +82,21 @@
<input type="checkbox" name="edit_role" id="edit_role" {% if content.role_edit() %}checked{% endif %}> <input type="checkbox" name="edit_role" id="edit_role" {% if content.role_edit() %}checked{% endif %}>
<label for="edit_role">{{_('Allow Edit')}}</label> <label for="edit_role">{{_('Allow Edit')}}</label>
</div> </div>
<div class="form-group"> {% if not content.role_anonymous() %}
<input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}> <div class="form-group">
<label for="passwd_role">{{_('Allow Changing Password')}}</label> <input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}>
</div> <label for="passwd_role">{{_('Allow Changing Password')}}</label>
</div>
{% endif %}
{% endif %} {% endif %}
{% if g.user and g.user.role_admin() and not profile and not new_user %} {% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="delete"> {{_('Delete this user')}} <input type="checkbox" name="delete"> {{_('Delete this user')}}
</label> </label>
</div> </div>
{% endif %} {% endif %}
<button type="submit" class="btn btn-default">{{_('Submit')}}</button> <button type="submit" id="submit" class="btn btn-default">{{_('Submit')}}</button>
{% if not profile %} {% if not profile %}
<a href="{{ url_for('admin') }}" class="btn btn-default">{{_('Back')}}</a> <a href="{{ url_for('admin') }}" class="btn btn-default">{{_('Back')}}</a>
{% endif %} {% endif %}

View File

@ -5,9 +5,12 @@ from sqlalchemy import *
from sqlalchemy import exc from sqlalchemy import exc
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import * from sqlalchemy.orm import *
from flask_login import AnonymousUserMixin
import os import os
import config import config
import traceback
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
from flask_babel import gettext as _
dbpath = os.path.join(config.APP_DB_ROOT, "app.db") dbpath = os.path.join(config.APP_DB_ROOT, "app.db")
engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False) engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False)
@ -19,28 +22,11 @@ ROLE_DOWNLOAD = 2
ROLE_UPLOAD = 4 ROLE_UPLOAD = 4
ROLE_EDIT = 8 ROLE_EDIT = 8
ROLE_PASSWD = 16 ROLE_PASSWD = 16
ROLE_ANONYMOUS = 32
DEFAULT_PASS = "admin123" DEFAULT_PASS = "admin123"
class User(Base): class UserBase():
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique=True)
email = Column(String(120), unique=True, default="")
role = Column(SmallInteger, default=ROLE_USER)
password = Column(String)
kindle_mail = Column(String(120), default="")
shelf = relationship('Shelf', backref='user', lazy='dynamic')
downloads = relationship('Downloads', backref='user', lazy='dynamic')
locale = Column(String(2), default="en")
random_books = Column(Integer, default=1)
language_books = Column(Integer, default=1)
series_books = Column(Integer, default=1)
category_books = Column(Integer, default=1)
hot_books = Column(Integer, default=1)
default_language = Column(String(3), default="all")
def is_authenticated(self): def is_authenticated(self):
return True return True
@ -74,6 +60,12 @@ class User(Base):
else: else:
return False return False
def role_anonymous(self):
if self.role is not None:
return True if self.role & ROLE_ANONYMOUS == ROLE_ANONYMOUS else False
else:
return False
def is_active(self): def is_active(self):
return True return True
@ -105,6 +97,52 @@ class User(Base):
return '<User %r>' % self.nickname return '<User %r>' % self.nickname
class User(UserBase,Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique=True)
email = Column(String(120), unique=True, default="")
role = Column(SmallInteger, default=ROLE_USER)
password = Column(String)
kindle_mail = Column(String(120), default="")
shelf = relationship('Shelf', backref='user', lazy='dynamic')
downloads = relationship('Downloads', backref='user', lazy='dynamic')
locale = Column(String(2), default="en")
random_books = Column(Integer, default=1)
language_books = Column(Integer, default=1)
series_books = Column(Integer, default=1)
category_books = Column(Integer, default=1)
hot_books = Column(Integer, default=1)
default_language = Column(String(3), default="all")
class Anonymous(AnonymousUserMixin,UserBase):
def __init__(self):
self.loadSettings()
def loadSettings(self):
data=session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first()
self.nickname = data.nickname
self.role = data.role
self.random_books = data.random_books
self.default_language = data.default_language
self.language_books = data.language_books
self.series_books = data.series_books
self.category_books = data.category_books
self.hot_books = data.hot_books
self.default_language = data.default_language
def role_admin(self):
return False
def is_active(self):
return False
def is_anonymous(self):
return config.ANON_BROWSE
class Shelf(Base): class Shelf(Base):
__tablename__ = 'shelf' __tablename__ = 'shelf'
@ -155,6 +193,8 @@ class Settings(Base):
def migrate_Database(): def migrate_Database():
if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None:
create_anonymous_user()
try: try:
session.query(exists().where(User.random_books)).scalar() session.query(exists().where(User.random_books)).scalar()
session.commit() session.commit()
@ -213,6 +253,20 @@ def get_mail_settings():
return data return data
def create_anonymous_user():
user = User()
user.nickname = _("Guest")
user.email='no@email'
user.role = ROLE_ANONYMOUS
user.password = generate_password_hash('1')
session.add(user)
try:
session.commit()
except:
session.rollback()
pass
def create_admin_user(): def create_admin_user():
user = User() user = User()
@ -236,6 +290,7 @@ if not os.path.exists(dbpath):
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
create_default_config() create_default_config()
create_admin_user() create_admin_user()
create_anonymous_user()
except Exception: except Exception:
pass pass
else: else:

View File

@ -14,7 +14,7 @@ from sqlalchemy.sql.expression import func
from sqlalchemy.sql.expression import false from sqlalchemy.sql.expression import false
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from math import ceil from math import ceil
from flask_login import LoginManager, login_user, logout_user, login_required, current_user, AnonymousUserMixin from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_principal import Principal, Identity, AnonymousIdentity, identity_changed from flask_principal import Principal, Identity, AnonymousIdentity, identity_changed
from flask_babel import Babel from flask_babel import Babel
from flask_babel import gettext as _ from flask_babel import gettext as _
@ -115,49 +115,10 @@ global global_queue
global_queue = None global_queue = None
class Anonymous(AnonymousUserMixin):
def __init__(self):
self.nickname = 'Guest'
self.role = -1
def role_admin(self):
return False
def role_download(self):
return False
def role_upload(self):
return False
def role_edit(self):
return False
def filter_language(self):
return 'all'
def show_random_books(self):
return True
def show_hot_books(self):
return True
def show_series(self):
return True
def show_category(self):
return True
def show_language(self):
return True
def is_anonymous(self):
return config.ANON_BROWSE
lm = LoginManager(app) lm = LoginManager(app)
lm.init_app(app) lm.init_app(app)
lm.login_view = 'login' lm.login_view = 'login'
lm.anonymous_user = Anonymous lm.anonymous_user = ub.Anonymous
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
@ -237,9 +198,9 @@ def requires_basic_auth_if_no_ano(f):
# simple pagination for the feed # simple pagination for the feed
class Pagination(object): class Pagination(object):
def __init__(self, page, per_page, total_count): def __init__(self, page, per_page, total_count):
self.page = page self.page = int(page)
self.per_page = per_page self.per_page = int(per_page)
self.total_count = total_count self.total_count = int(total_count)
@property @property
def next_offset(self): def next_offset(self):
@ -247,7 +208,7 @@ class Pagination(object):
@property @property
def previous_offset(self): def previous_offset(self):
return int((self.page-1) * self.per_page) return int((self.page-2) * self.per_page)
@property @property
def last_offset(self): def last_offset(self):
@ -453,11 +414,17 @@ def feed_osd():
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/opds/search/<query>")
def feed_cc_search(query):
return feed_search(query.strip())
@app.route("/opds/search", methods=["GET"]) @app.route("/opds/search", methods=["GET"])
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_search(): def feed_normal_search():
term = request.args.get("query").strip() return feed_search(request.args.get("query").strip())
def feed_search(term):
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
@ -466,8 +433,8 @@ def feed_search():
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")), entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
db.Books.authors.any(db.Authors.name.like("%" + term + "%")), db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
db.Books.title.like("%" + term + "%"))).filter(filter).all() db.Books.title.like("%" + term + "%"))).filter(filter).all()
pagination = Pagination( 1,len(entries),len(entries))
xml = render_template('feed.xml', searchterm=term, entries=entries) xml = render_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
else: else:
xml = render_template('feed.xml', searchterm="") xml = render_template('feed.xml', searchterm="")
response = make_response(xml) response = make_response(xml)
@ -1145,13 +1112,14 @@ def read_book(book_id, format):
@app.route("/download/<int:book_id>/<format>") @app.route("/download/<int:book_id>/<format>")
@login_required @login_required_if_no_ano
@download_required @download_required
def get_download_link(book_id, format): def get_download_link(book_id, format):
format = format.split(".")[0] format = format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first()
helper.update_download(book_id, int(current_user.id)) if current_user.is_authenticated: # collect downloaded books only for registered user and not for anonymous user
helper.update_download(book_id, int(current_user.id))
author = helper.get_normalized_author(book.author_sort) author = helper.get_normalized_author(book.author_sort)
file_name = book.title file_name = book.title
if len(author) > 0: if len(author) > 0:
@ -1392,7 +1360,7 @@ def show_shelf(shelf_id):
@app.route("/shelf/order/<int:shelf_id>", methods=["GET", "POST"]) @app.route("/shelf/order/<int:shelf_id>", methods=["GET", "POST"])
@login_required_if_no_ano @login_required
def order_shelf(shelf_id): def order_shelf(shelf_id):
if request.method == "POST": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
@ -1619,7 +1587,7 @@ def edit_user(user_id):
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success") flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
return redirect(url_for('admin')) return redirect(url_for('admin'))
else: else:
if to_save["password"]: if "password" in to_save and to_save["password"]:
content.password = generate_password_hash(to_save["password"]) content.password = generate_password_hash(to_save["password"])
if "admin_role" in to_save and not content.role_admin(): if "admin_role" in to_save and not content.role_admin():
@ -1663,7 +1631,7 @@ def edit_user(user_id):
content.hot_books = 1 content.hot_books = 1
if "default_language" in to_save: if "default_language" in to_save:
content.default_language = to_save["default_language"] content.default_language = to_save["default_language"]
if to_save["locale"]: if "locale" in to_save and to_save["locale"]:
content.locale = to_save["locale"] content.locale = to_save["locale"]
if to_save["email"] and to_save["email"] != content.email: if to_save["email"] and to_save["email"] != content.email:
content.email = to_save["email"] content.email = to_save["email"]
@ -1680,7 +1648,7 @@ def edit_user(user_id):
@app.route("/admin/book/<int:book_id>", methods=['GET', 'POST']) @app.route("/admin/book/<int:book_id>", methods=['GET', 'POST'])
@login_required @login_required_if_no_ano
@edit_required @edit_required
def edit_book(book_id): def edit_book(book_id):
# create the function for sorting... # create the function for sorting...
@ -1889,7 +1857,7 @@ def edit_book(book_id):
@app.route("/upload", methods=["GET", "POST"]) @app.route("/upload", methods=["GET", "POST"])
@login_required @login_required_if_no_ano
@upload_required @upload_required
def upload(): def upload():
if not config.UPLOADING: if not config.UPLOADING: