From 0107c52b66491121541d18974d5674530c42929e Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Thu, 12 Jan 2017 20:43:36 +0100 Subject: [PATCH] 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 --- cps/templates/admin.html | 5 +- cps/templates/detail.html | 14 ++++-- cps/templates/feed.xml | 5 +- cps/templates/layout.html | 5 +- cps/templates/user_edit.html | 31 +++++++----- cps/ub.py | 93 ++++++++++++++++++++++++++++-------- cps/web.py | 80 ++++++++++--------------------- 7 files changed, 133 insertions(+), 100 deletions(-) diff --git a/cps/templates/admin.html b/cps/templates/admin.html index f99a44ad..e16bc11c 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -13,9 +13,9 @@ {{_('Upload')}} {{_('Edit')}} {{_('Passwd')}} - {% for user in content %} + {% if not user.role_anonymous() or config.ANON_BROWSE %} {{user.nickname}} {{user.email}} @@ -26,7 +26,8 @@ {% if user.role_upload() %}{% else %}{% endif %} {% if user.role_edit() %}{% else %}{% endif %} {% if user.role_passwd() %}{% else %}{% endif %} - + + {% endif %} {% endfor %}
{{_('Add new user')}}
diff --git a/cps/templates/detail.html b/cps/templates/detail.html index 682eff92..ca5a0b4f 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -104,11 +104,11 @@ {{entry.comments[0].text|safe}} {% endif %} - {% if g.user.is_authenticated %} +

- + {% if g.user.is_authenticated %} {% if g.user.shelf.all() or g.public_shelfes %} {% endif %} - + {% endif %} {% if g.user.role_edit() %} diff --git a/cps/templates/feed.xml b/cps/templates/feed.xml index 296ddd2e..dd7d58a0 100644 --- a/cps/templates/feed.xml +++ b/cps/templates/feed.xml @@ -10,12 +10,11 @@ +{% if pagination.has_prev %} - +{% endif %} {% if pagination.has_next %} {{shelf.name}} {% endfor %} {% if not g.user.is_anonymous() %} -
  • {{_('Create a Shelf')}}
  • + + {% endif %} {% endif %} -
  • {{_('About')}}
  • + diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index 090975c5..d8403fc1 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -13,7 +13,7 @@ - {% 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() %}
    @@ -23,6 +23,7 @@
    + {% if not content.role_anonymous() %}
    + {% endif %}
    + {% endif %}
    @@ -79,19 +82,21 @@
    -
    - - -
    + {% if not content.role_anonymous() %} +
    + + +
    + {% 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() %} +
    + +
    {% endif %} - + {% if not profile %} {{_('Back')}} {% endif %} diff --git a/cps/ub.py b/cps/ub.py index 40839aa1..be802d8e 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -5,9 +5,12 @@ from sqlalchemy import * from sqlalchemy import exc from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import * +from flask_login import AnonymousUserMixin import os import config +import traceback from werkzeug.security import generate_password_hash +from flask_babel import gettext as _ dbpath = os.path.join(config.APP_DB_ROOT, "app.db") engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False) @@ -19,28 +22,11 @@ ROLE_DOWNLOAD = 2 ROLE_UPLOAD = 4 ROLE_EDIT = 8 ROLE_PASSWD = 16 +ROLE_ANONYMOUS = 32 DEFAULT_PASS = "admin123" -class User(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 UserBase(): def is_authenticated(self): return True @@ -74,6 +60,12 @@ class User(Base): else: 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): return True @@ -105,6 +97,52 @@ class User(Base): return '' % 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): __tablename__ = 'shelf' @@ -155,6 +193,8 @@ class Settings(Base): def migrate_Database(): + if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None: + create_anonymous_user() try: session.query(exists().where(User.random_books)).scalar() session.commit() @@ -213,6 +253,20 @@ def get_mail_settings(): 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(): user = User() @@ -236,6 +290,7 @@ if not os.path.exists(dbpath): Base.metadata.create_all(engine) create_default_config() create_admin_user() + create_anonymous_user() except Exception: pass else: diff --git a/cps/web.py b/cps/web.py index 2ff149e4..737551be 100755 --- a/cps/web.py +++ b/cps/web.py @@ -14,7 +14,7 @@ from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import false from sqlalchemy.exc import IntegrityError 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_babel import Babel from flask_babel import gettext as _ @@ -115,49 +115,10 @@ global global_queue 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.init_app(app) lm.login_view = 'login' -lm.anonymous_user = Anonymous +lm.anonymous_user = ub.Anonymous 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 class Pagination(object): def __init__(self, page, per_page, total_count): - self.page = page - self.per_page = per_page - self.total_count = total_count + self.page = int(page) + self.per_page = int(per_page) + self.total_count = int(total_count) @property def next_offset(self): @@ -247,7 +208,7 @@ class Pagination(object): @property def previous_offset(self): - return int((self.page-1) * self.per_page) + return int((self.page-2) * self.per_page) @property def last_offset(self): @@ -453,11 +414,17 @@ def feed_osd(): response.headers["Content-Type"] = "application/xml" return response +@app.route("/opds/search/") +def feed_cc_search(query): + return feed_search(query.strip()) + @app.route("/opds/search", methods=["GET"]) @requires_basic_auth_if_no_ano -def feed_search(): - term = request.args.get("query").strip() +def feed_normal_search(): + return feed_search(request.args.get("query").strip()) + +def feed_search(term): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) 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 + "%")), db.Books.authors.any(db.Authors.name.like("%" + term + "%")), db.Books.title.like("%" + term + "%"))).filter(filter).all() - - xml = render_template('feed.xml', searchterm=term, entries=entries) + pagination = Pagination( 1,len(entries),len(entries)) + xml = render_template('feed.xml', searchterm=term, entries=entries, pagination=pagination) else: xml = render_template('feed.xml', searchterm="") response = make_response(xml) @@ -1145,13 +1112,14 @@ def read_book(book_id, format): @app.route("/download//") -@login_required +@login_required_if_no_ano @download_required def get_download_link(book_id, format): format = format.split(".")[0] 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() - 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) file_name = book.title if len(author) > 0: @@ -1392,7 +1360,7 @@ def show_shelf(shelf_id): @app.route("/shelf/order/", methods=["GET", "POST"]) -@login_required_if_no_ano +@login_required def order_shelf(shelf_id): if request.method == "POST": 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") return redirect(url_for('admin')) else: - if to_save["password"]: + if "password" in to_save and to_save["password"]: content.password = generate_password_hash(to_save["password"]) if "admin_role" in to_save and not content.role_admin(): @@ -1663,7 +1631,7 @@ def edit_user(user_id): content.hot_books = 1 if "default_language" in to_save: 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"] if to_save["email"] and to_save["email"] != content.email: content.email = to_save["email"] @@ -1680,7 +1648,7 @@ def edit_user(user_id): @app.route("/admin/book/", methods=['GET', 'POST']) -@login_required +@login_required_if_no_ano @edit_required def edit_book(book_id): # create the function for sorting... @@ -1889,7 +1857,7 @@ def edit_book(book_id): @app.route("/upload", methods=["GET", "POST"]) -@login_required +@login_required_if_no_ano @upload_required def upload(): if not config.UPLOADING: