diff --git a/cps/static/css/style.css b/cps/static/css/style.css index 8b299002..47ad9e24 100644 --- a/cps/static/css/style.css +++ b/cps/static/css/style.css @@ -53,3 +53,6 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te .spinner2 {margin:0 41%;} .block-label {display: block;} + +.author-bio img {margin: 0 1em 1em 0;} +.author-link img {display: inline-block;max-width: 100px;} \ No newline at end of file diff --git a/cps/static/img/goodreads.svg b/cps/static/img/goodreads.svg new file mode 100644 index 00000000..f89130e9 --- /dev/null +++ b/cps/static/img/goodreads.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cps/static/js/main.js b/cps/static/js/main.js index d759a3b8..6c41bdf2 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -2,6 +2,20 @@ var displaytext; var updateTimerID; var updateText; +// Generic control/related handler to show/hide fields based on a checkbox' value +// e.g. +// +//
...
+$(document).on("change", "input[type=\"checkbox\"][data-control]", function () { + var $this = $(this); + var name = $this.data("control"); + var showOrHide = $this.prop("checked"); + + $("[data-related=\""+name+"\"]").each(function () { + $(this).toggle(showOrHide); + }); +}); + $(function() { function restartTimer() { @@ -121,6 +135,8 @@ $(function() { }); }); + $("input[data-control]").trigger("change"); + $(window).resize(function(event) { $(".discover .row").isotope("reLayout"); }); diff --git a/cps/templates/author.html b/cps/templates/author.html new file mode 100644 index 00000000..4d194f99 --- /dev/null +++ b/cps/templates/author.html @@ -0,0 +1,65 @@ +{% extends "layout.html" %} +{% block body %} +

{{title}}

+ +{% if author is not none %} +
+ {%if author['image_url'] is not none %} + {{author.name}} + {% endif %} + + {%if author.about is not none %} +

{{author.about|safe}}

+ {% endif %} +
+ + + Goodreads + + +
+{% endif %} + +
+
+ {% if entries[0] %} + {% for entry in entries %} +
+ +
+

{{entry.title|shortentitle}}

+

+ {% for author in entry.authors %} + {{author.name}} + {% if not loop.last %} + & + {% endif %} + {% endfor %} +

+ {% if entry.ratings.__len__() > 0 %} +
+ {% for number in range((entry.ratings[0].rating/2)|int(2)) %} + + {% if loop.last and loop.index < 5 %} + {% for numer in range(5 - loop.index) %} + + {% endfor %} + {% endif %} + {% endfor %} +
+ {% endif %} +
+
+ {% endfor %} + {% endif %} +
+
+{% endblock %} diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 52b5d09c..ab901c6b 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -97,6 +97,24 @@ + {% if goodreads %} +
+ + + {{_('Obtain an API Key')}} +
+
+
+ + +
+
+ + +
+
+ {% endif %} +

{{_('Default Settings for new users')}}

diff --git a/cps/ub.py b/cps/ub.py index dc377cba..1a8b0b0c 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -263,6 +263,9 @@ class Settings(Base): config_google_drive_watch_changes_response = Column(String) config_columns_to_ignore = Column(String) config_remote_login = Column(Boolean) + config_use_goodreads = Column(Boolean) + config_goodreads_api_key = Column(String) + config_goodreads_api_secret = Column(String) def __repr__(self): pass @@ -320,6 +323,9 @@ class Config: self.db_configured = bool(self.config_calibre_dir is not None and (not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db'))) self.config_remote_login = data.config_remote_login + self.config_use_goodreads = data.config_use_goodreads + self.config_goodreads_api_key = data.config_goodreads_api_key + self.config_goodreads_api_secret = data.config_goodreads_api_secret @property def get_main_dir(self): @@ -475,6 +481,13 @@ def migrate_Database(): except exc.OperationalError: conn = engine.connect() conn.execute("ALTER TABLE Settings ADD column `config_remote_login` INTEGER DEFAULT 0") + try: + session.query(exists().where(Settings.config_use_goodreads)).scalar() + except exc.OperationalError: + conn = engine.connect() + conn.execute("ALTER TABLE Settings ADD column `config_use_goodreads` INTEGER DEFAULT 0") + conn.execute("ALTER TABLE Settings ADD column `config_goodreads_api_key` String DEFAULT ''") + conn.execute("ALTER TABLE Settings ADD column `config_goodreads_api_secret` String DEFAULT ''") def clean_database(): # Remove expired remote login tokens diff --git a/cps/web.py b/cps/web.py index 5d4d0fef..92ea27b1 100755 --- a/cps/web.py +++ b/cps/web.py @@ -7,6 +7,12 @@ try: except ImportError: gdrive_support = False +try: + from goodreads import client as gr_client + goodreads_support = True +except ImportError: + goodreads_support = False + import mimetypes import logging from logging.handlers import RotatingFileHandler @@ -1086,10 +1092,16 @@ def author_list(): def author(book_id, page): entries, random, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id), db.Books.timestamp.desc()) - name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name if entries: - return render_title_template('index.html', random=random, entries=entries, pagination=pagination, - title=_(u"Author: %(name)s", name=name)) + name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name + + author_info = None + if goodreads_support and config.config_use_goodreads: + gc = gr_client.GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret) + author_info = gc.find_author(author_name=name) + + return render_title_template('author.html', entries=entries, pagination=pagination, + title=name, author=author_info) else: flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect(url_for("index")) @@ -2289,11 +2301,20 @@ def configuration_helper(origin): content.config_anonbrowse = 1 if "config_public_reg" in to_save and to_save["config_public_reg"] == "on": content.config_public_reg = 1 - content.config_remote_login = ("config_remote_login" in to_save and to_save["config_remote_login"] == "on") + # Remote login configuration + content.config_remote_login = ("config_remote_login" in to_save and to_save["config_remote_login"] == "on") if not content.config_remote_login: ub.session.query(ub.RemoteAuthToken).delete() + # Goodreads configuration + content.config_use_goodreads = ("config_use_goodreads" in to_save and to_save["config_use_goodreads"] == "on") + if "config_goodreads_api_key" in to_save: + content.config_goodreads_api_key = to_save["config_goodreads_api_key"] + if "config_goodreads_api_secret" in to_save: + content.config_goodreads_api_secret = to_save["config_goodreads_api_secret"] + + content.config_default_role = 0 if "admin_role" in to_save: content.config_default_role = content.config_default_role + ub.ROLE_ADMIN @@ -2324,13 +2345,13 @@ def configuration_helper(origin): except e: flash(e, category="error") return render_title_template("config_edit.html", content=config, origin=origin, gdrive=gdrive_support, - title=_(u"Basic Configuration")) + goodreads=goodreads_support, title=_(u"Basic Configuration")) if db_change: reload(db) if not db.setup_db(): flash(_(u'DB location is not valid, please enter correct path'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, gdrive=gdrive_support, - title=_(u"Basic Configuration")) + goodreads=goodreads_support, title=_(u"Basic Configuration")) if reboot_required: # db.engine.dispose() # ToDo verify correct ub.session.close() @@ -2344,7 +2365,7 @@ def configuration_helper(origin): success = True return render_title_template("config_edit.html", origin=origin, success=success, content=config, show_authenticate_google_drive=not is_gdrive_ready(), gdrive=gdrive_support, - title=_(u"Basic Configuration")) + goodreads=goodreads_support, title=_(u"Basic Configuration")) @app.route("/admin/user/new", methods=["GET", "POST"]) diff --git a/optional-requirements.txt b/optional-requirements.txt index 71c7b4ea..5e4c4414 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -11,3 +11,4 @@ PyYAML==3.12 rsa==3.4.2 six==1.10.0 uritemplate==3.0.0 +goodreads==0.3.2 \ No newline at end of file