From f5235b1d4c9ee55c43fb124ba3cecb56931c6767 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 9 Feb 2019 18:46:36 +0100 Subject: [PATCH] Uploader progress bar Fixes imports and routes Merge branch 'master' into Develop Finished routing --- cps/admin.py | 68 ++++++++++- cps/editbooks.py | 51 ++++---- cps/oauth_bb.py | 10 +- cps/static/css/upload.css | 8 ++ cps/static/js/uploadprogress.js | 198 ++++++++++++++++++++++++++++++++ cps/templates/author.html | 8 +- cps/templates/book_edit.html | 4 +- cps/templates/config_edit.html | 6 +- cps/templates/detail.html | 18 +-- cps/templates/discover.html | 8 +- cps/templates/email_edit.html | 7 +- cps/templates/http_error.html | 2 +- cps/templates/languages.html | 2 +- cps/templates/layout.html | 24 ++-- cps/templates/listenmp3.html | 8 +- cps/templates/login.html | 6 +- cps/templates/read.html | 4 +- cps/templates/readcbr.html | 2 +- cps/templates/readpdf.html | 2 +- cps/templates/readtxt.html | 6 +- cps/templates/register.html | 4 +- cps/templates/remote_login.html | 4 +- cps/templates/search.html | 4 +- cps/templates/shelf_order.html | 4 +- cps/templates/shelfdown.html | 14 +-- cps/templates/user_edit.html | 8 +- cps/ub.py | 2 + cps/uploader.py | 21 ++-- cps/web.py | 77 ++----------- optional-requirements-ldap.txt | 1 - optional-requirements.txt | 2 + 31 files changed, 393 insertions(+), 190 deletions(-) create mode 100644 cps/static/css/upload.css create mode 100644 cps/static/js/uploadprogress.js delete mode 100644 optional-requirements-ldap.txt diff --git a/cps/admin.py b/cps/admin.py index cd6ff60c..f0e90e5c 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -22,11 +22,11 @@ # along with this program. If not, see . import os -from flask import Blueprint -from flask import abort, request -from flask_login import login_required, current_user -from web import admin_required, render_title_template, flash, redirect, url_for, before_request, logout_user, \ - speaking_language, unconfigured +from flask import Blueprint, flash, redirect, url_for +from flask import abort, request, make_response +from flask_login import login_required, current_user, logout_user +from web import admin_required, render_title_template, before_request, speaking_language, unconfigured, \ + login_required_if_no_ano, check_valid_domain from cps import db, ub, Server, get_locale, config, app, updater_thread, babel import json from datetime import datetime, timedelta @@ -36,9 +36,9 @@ from flask_babel import gettext as _ from babel import Locale as LC from sqlalchemy.exc import IntegrityError from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders -from web import login_required_if_no_ano, check_valid_domain import helper from werkzeug.security import generate_password_hash +from sqlalchemy.sql.expression import text try: from goodreads.client import GoodreadsClient @@ -218,6 +218,62 @@ def view_configuration(): title=_(u"UI Configuration"), page="uiconfig") +@admi.route("/ajax/editdomain", methods=['POST']) +@login_required +@admin_required +def edit_domain(): + # POST /post + # name: 'username', //name of field (column in db) + # pk: 1 //primary key (record id) + # value: 'superuser!' //new value + vals = request.form.to_dict() + answer = ub.session.query(ub.Registration).filter(ub.Registration.id == vals['pk']).first() + # domain_name = request.args.get('domain') + answer.domain = vals['value'].replace('*', '%').replace('?', '_').lower() + ub.session.commit() + return "" + + +@admi.route("/ajax/adddomain", methods=['POST']) +@login_required +@admin_required +def add_domain(): + domain_name = request.form.to_dict()['domainname'].replace('*', '%').replace('?', '_').lower() + check = ub.session.query(ub.Registration).filter(ub.Registration.domain == domain_name).first() + if not check: + new_domain = ub.Registration(domain=domain_name) + ub.session.add(new_domain) + ub.session.commit() + return "" + + +@admi.route("/ajax/deletedomain", methods=['POST']) +@login_required +@admin_required +def delete_domain(): + domain_id = request.form.to_dict()['domainid'].replace('*', '%').replace('?', '_').lower() + ub.session.query(ub.Registration).filter(ub.Registration.id == domain_id).delete() + ub.session.commit() + # If last domain was deleted, add all domains by default + if not ub.session.query(ub.Registration).count(): + new_domain = ub.Registration(domain="%.%") + ub.session.add(new_domain) + ub.session.commit() + return "" + + +@admi.route("/ajax/domainlist") +@login_required +@admin_required +def list_domain(): + answer = ub.session.query(ub.Registration).all() + json_dumps = json.dumps([{"domain": r.domain.replace('%', '*').replace('_', '?'), "id": r.id} for r in answer]) + js = json.dumps(json_dumps.replace('"', "'")).lstrip('"').strip('"') + response = make_response(js.replace("'", '"')) + response.headers["Content-Type"] = "application/json; charset=utf-8" + return response + + @admi.route("/config", methods=["GET", "POST"]) @unconfigured def basic_configuration(): diff --git a/cps/editbooks.py b/cps/editbooks.py index 4ca5af1f..2c20b8a9 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -22,12 +22,13 @@ # along with this program. If not, see . # opds routing functions -from cps import config, language_table, get_locale, app, ub -from flask import request, flash, redirect, url_for, abort, Markup +from cps import config, language_table, get_locale, app, ub, global_WorkerThread +from flask import request, flash, redirect, url_for, abort, Markup, Response from flask import Blueprint import datetime import db import os +import json from flask_babel import gettext as _ from uuid import uuid4 import helper @@ -206,9 +207,9 @@ def delete_book(book_id, book_format): # book not found app.logger.info('Book with id "'+str(book_id)+'" could not be deleted') if book_format: - return redirect(url_for('edit_book', book_id=book_id)) + return redirect(url_for('editbook.edit_book', book_id=book_id)) else: - return redirect(url_for('index')) + return redirect(url_for('web.index')) def render_edit_book(book_id): @@ -341,10 +342,10 @@ def upload_single_file(request, book, book_id): if file_ext not in EXTENSIONS_UPLOAD: flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) else: flash(_('File to be uploaded must have an extension'), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) file_name = book.path.rsplit('/', 1)[-1] filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path)) @@ -356,12 +357,12 @@ def upload_single_file(request, book, book_id): os.makedirs(filepath) except OSError: flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) try: requested_file.save(saved_filename) except OSError: flash(_(u"Failed to store file %(file)s.", file=saved_filename), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) file_size = os.path.getsize(saved_filename) is_format = db.session.query(db.Data).filter(db.Data.book == book_id).\ @@ -378,7 +379,7 @@ def upload_single_file(request, book, book_id): # Queue uploader info uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title) - helper.global_WorkerThread.add_upload(current_user.nickname, + global_WorkerThread.add_upload(current_user.nickname, "" + uploadText + "") def upload_cover(request, book): @@ -397,17 +398,17 @@ def upload_cover(request, book): except OSError: flash(_(u"Failed to create path for cover %(path)s (Permission denied).", cover=filepath), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) try: requested_file.save(saved_filename) # im=Image.open(saved_filename) book.has_cover = 1 except OSError: flash(_(u"Failed to store cover-file %(cover)s.", cover=saved_filename), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) except IOError: flash(_(u"Cover-file is not a valid image file" % saved_filename), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) @editbook.route("/admin/book/", methods=['GET', 'POST']) @login_required_if_no_ano @@ -554,7 +555,7 @@ def edit_book(book_id): if config.config_use_google_drive: gdriveutils.updateGdriveCalibreFromLocal() if "detail_view" in to_save: - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) else: flash(_("Metadata successfully updated"), category="success") return render_edit_book(book_id) @@ -566,7 +567,7 @@ def edit_book(book_id): app.logger.exception(e) db.session.rollback() flash(_("Error editing book, please check logfile for details"), category="error") - return redirect(url_for('show_book', book_id=book.id)) + return redirect(url_for('web.show_book', book_id=book.id)) @editbook.route("/upload", methods=["GET", "POST"]) @@ -704,8 +705,8 @@ def upload(): if error: flash(error, category="error") uploadText=_(u"File %(file)s uploaded", file=book.title) - helper.global_WorkerThread.add_upload(current_user.nickname, - "" + uploadText + "") + global_WorkerThread.add_upload(current_user.nickname, + "" + uploadText + "") # create data for displaying display Full language name instead of iso639.part3language if db_language is not None: @@ -714,19 +715,13 @@ def upload(): for author in db_book.authors: author_names.append(author.name) if len(request.files.getlist("btn-upload")) < 2: - cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns. - datatype.notin_(db.cc_exceptions)).all() if current_user.role_edit() or current_user.role_admin(): - return render_title_template('book_edit.html', book=book, authors=author_names, - cc=cc, title=_(u"edit metadata"), page="upload") - book_in_shelfs = [] - kindle_list = helper.check_send_to_kindle(book) - reader_list = helper.check_read_formats(book) - - return render_title_template('detail.html', entry=book, cc=cc, - title=book.title, books_shelfs=book_in_shelfs, kindle_list=kindle_list, - reader_list=reader_list, page="upload") - return redirect(url_for("web.index")) + resp = {"location": url_for('editbook.edit_book', book_id=db_book.id)} + return Response(json.dumps(resp), mimetype='application/json') + else: + resp = {"location": url_for('web.show_book', book_id=db_book.id)} + return Response(json.dumps(resp), mimetype='application/json') + return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') @editbook.route("/admin/book/convert/", methods=['POST']) diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py index 34e71fde..0be03617 100644 --- a/cps/oauth_bb.py +++ b/cps/oauth_bb.py @@ -187,7 +187,7 @@ def bind_oauth_or_register(provider, provider_user_id, redirect_url): except Exception as e: app.logger.exception(e) ub.session.rollback() - return redirect(url_for('register')) + return redirect(url_for('web.register')) except NoResultFound: return redirect(url_for(redirect_url)) @@ -247,7 +247,7 @@ def github_error(blueprint, error, error_description=None, error_uri=None): flash(msg, category="error") -@web.route('/github') +@oauth.route('/github') @github_oauth_required def github_login(): if not github.authorized: @@ -260,13 +260,13 @@ def github_login(): return redirect(url_for('login')) -@web.route('/unlink/github', methods=["GET"]) +@oauth.route('/unlink/github', methods=["GET"]) @login_required def github_login_unlink(): return unlink_oauth(github_blueprint.name) -@web.route('/google') +@oauth.route('/google') @google_oauth_required def google_login(): if not google.authorized: @@ -293,7 +293,7 @@ def google_error(blueprint, error, error_description=None, error_uri=None): flash(msg, category="error") -@web.route('/unlink/google', methods=["GET"]) +@oauth.route('/unlink/google', methods=["GET"]) @login_required def google_login_unlink(): return unlink_oauth(google_blueprint.name) diff --git a/cps/static/css/upload.css b/cps/static/css/upload.css new file mode 100644 index 00000000..8192bd39 --- /dev/null +++ b/cps/static/css/upload.css @@ -0,0 +1,8 @@ +@media (min-device-width: 768px) { + .modal-dialog { + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -50%) !important; + } +} diff --git a/cps/static/js/uploadprogress.js b/cps/static/js/uploadprogress.js new file mode 100644 index 00000000..b28d17f3 --- /dev/null +++ b/cps/static/js/uploadprogress.js @@ -0,0 +1,198 @@ +/* + * bootstrap-uploadprogress + * github: https://github.com/jakobadam/bootstrap-uploadprogress + * + * Copyright (c) 2015 Jakob Aarøe Dam + * Version 1.0.0 + * Licensed under the MIT license. + */ +(function($){ + "use strict"; + + $.support.xhrFileUpload = !!(window.FileReader && window.ProgressEvent); + $.support.xhrFormData = !!window.FormData; + + if(!$.support.xhrFileUpload || !$.support.xhrFormData){ + // skip decorating form + return; + } + + var template = ''; + + var UploadProgress = function(element, options){ + this.options = options; + this.$element = $(element); + }; + + UploadProgress.prototype = { + + constructor: function() { + this.$form = this.$element; + this.$form.on('submit', $.proxy(this.submit, this)); + this.$modal = $(this.options.template); + this.$modal_message = this.$modal.find('.modal-message'); + this.$modal_title = this.$modal.find('.modal-title'); + this.$modal_footer = this.$modal.find('.modal-footer'); + this.$modal_bar = this.$modal.find('.progress-bar'); + + this.$modal.on('hidden.bs.modal', $.proxy(this.reset, this)); + }, + + reset: function(){ + this.$modal_title = this.$modal_title.text('Uploading'); + this.$modal_footer.hide(); + this.$modal_bar.addClass('progress-bar-success'); + this.$modal_bar.removeClass('progress-bar-danger'); + if(this.xhr){ + this.xhr.abort(); + } + }, + + submit: function(e) { + e.preventDefault(); + + this.$modal.modal({ + backdrop: 'static', + keyboard: false + }); + + // We need the native XMLHttpRequest for the progress event + var xhr = new XMLHttpRequest(); + this.xhr = xhr; + + xhr.addEventListener('load', $.proxy(this.success, this, xhr)); + xhr.addEventListener('error', $.proxy(this.error, this, xhr)); + //xhr.addEventListener('abort', function(){}); + + xhr.upload.addEventListener('progress', $.proxy(this.progress, this)); + + var form = this.$form; + + xhr.open(form.attr('method'), form.attr("action")); + xhr.setRequestHeader('X-REQUESTED-WITH', 'XMLHttpRequest'); + + var data = new FormData(form.get(0)); + xhr.send(data); + }, + + success: function(xhr) { + if(xhr.status == 0 || xhr.status >= 400){ + // HTTP 500 ends up here!?! + return this.error(xhr); + } + this.set_progress(100); + var url; + var content_type = xhr.getResponseHeader('Content-Type'); + + // make it possible to return the redirect URL in + // a JSON response + if(content_type.indexOf('application/json') !== -1){ + var response = $.parseJSON(xhr.responseText); + console.log(response); + url = response.location; + } + else{ + url = this.options.redirect_url; + } + window.location.href = url; + }, + + // handle form error + // we replace the form with the returned one + error: function(xhr){ + this.$modal_title.text('Upload failed'); + + this.$modal_bar.removeClass('progress-bar-success'); + this.$modal_bar.addClass('progress-bar-danger'); + this.$modal_footer.show(); + + var content_type = xhr.getResponseHeader('Content-Type'); + + // Replace the contents of the form, with the returned html + if(xhr.status === 422){ + var new_html = $.parseHTML(xhr.responseText); + this.replace_form(new_html); + this.$modal.modal('hide'); + } + // Write the error response to the document. + else{ + var response_text = xhr.responseText; + if(content_type.indexOf('text/plain') !== -1){ + response_text = '
' + response_text + '
'; + } + document.write(xhr.responseText); + } + }, + + set_progress: function(percent){ + var txt = percent + '%'; + if (percent == 100) { + txt = this.options.uploaded_msg; + } + this.$modal_bar.attr('aria-valuenow', percent); + this.$modal_bar.text(txt); + this.$modal_bar.css('width', percent + '%'); + }, + + progress: function(/*ProgressEvent*/e){ + var percent = Math.round((e.loaded / e.total) * 100); + this.set_progress(percent); + }, + + // replace_form replaces the contents of the current form + // with the form in the html argument. + // We use the id of the current form to find the new form in the html + replace_form: function(html){ + var new_form; + var form_id = this.$form.attr('id'); + if(form_id !== undefined){ + new_form = $(html).find('#' + form_id); + } + else{ + new_form = $(html).find('form'); + } + + // add the filestyle again + new_form.find(':file').filestyle({buttonBefore: true}); + this.$form.html(new_form.children()); + } + }; + + $.fn.uploadprogress = function(options, value){ + return this.each(function(){ + var _options = $.extend({}, $.fn.uploadprogress.defaults, options); + var file_progress = new UploadProgress(this, _options); + file_progress.constructor(); + }); + }; + + $.fn.uploadprogress.defaults = { + template: template, + uploaded_msg: "Upload done, processing, please wait..." + //redirect_url: ... + + // need to customize stuff? Add here, and change code accordingly. + }; + +})(window.jQuery); diff --git a/cps/templates/author.html b/cps/templates/author.html index 2ebe8c4e..6d888845 100644 --- a/cps/templates/author.html +++ b/cps/templates/author.html @@ -27,21 +27,21 @@ {% for entry in entries %}
- +

{{entry.title|shortentitle}}

{% for author in entry.authors %} - {{author.name.replace('|',',')}} + {{author.name.replace('|',',')}} {% if not loop.last %} & {% endif %} diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html index 449a2d57..0a3f4bb9 100644 --- a/cps/templates/book_edit.html +++ b/cps/templates/book_edit.html @@ -53,7 +53,7 @@ {% endif %}

-
+
@@ -196,7 +196,7 @@
diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 6abe9685..a7d0bcb9 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -33,7 +33,7 @@ {% else %} {% if show_authenticate_google_drive and g.user.is_authenticated and content.config_use_google_drive %} {% else %} {% if show_authenticate_google_drive and g.user.is_authenticated and not content.config_use_google_drive %} @@ -56,10 +56,10 @@ {% else %} - Enable watch of metadata.db + Enable watch of metadata.db {% endif %} {% endif %} {% endif %} diff --git a/cps/templates/detail.html b/cps/templates/detail.html index e68bcf47..a459b89e 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -224,8 +224,8 @@ {% for shelf in g.user.shelf %} {% if not shelf.id in books_shelfs and shelf.is_public != 1 %}
  • - {{shelf.name}} @@ -236,8 +236,8 @@ {% for shelf in g.public_shelfes %} {% if not shelf.id in books_shelfs %}
  • - {{shelf.name}} @@ -251,8 +251,8 @@ {% if books_shelfs %} {% for shelf in g.user.shelf %} {% if shelf.id in books_shelfs and shelf.is_public != 1 %} - {{shelf.name}} @@ -261,8 +261,8 @@ {%endfor%} {% for shelf in g.public_shelfes %} {% if shelf.id in books_shelfs %} - {{shelf.name}} @@ -279,7 +279,7 @@ {% if g.user.role_edit() %} {% endif %} diff --git a/cps/templates/discover.html b/cps/templates/discover.html index e8e848e7..927fb717 100644 --- a/cps/templates/discover.html +++ b/cps/templates/discover.html @@ -8,18 +8,18 @@
    {% if entry.has_cover is defined %} - - {{ entry.title }} + + {{ entry.title }} {% endif %}
    - +

    {{entry.title|shortentitle}}

    {% for author in entry.authors %} - {{author.name.replace('|',',')|shortentitle(30)}} + {{author.name.replace('|',',')|shortentitle(30)}} {% if not loop.last %} & {% endif %} diff --git a/cps/templates/email_edit.html b/cps/templates/email_edit.html index 00b60640..bb5c60a0 100644 --- a/cps/templates/email_edit.html +++ b/cps/templates/email_edit.html @@ -41,16 +41,16 @@

    {% if g.allow_registration %}

    {{_('Allowed domains for registering')}}

    - +
    - +
    -
    +
    @@ -71,7 +71,6 @@
    diff --git a/cps/templates/http_error.html b/cps/templates/http_error.html index 3f7f73f7..98763cdc 100644 --- a/cps/templates/http_error.html +++ b/cps/templates/http_error.html @@ -20,7 +20,7 @@

    {{ error_code }}

    {{ error_name }}

    - {{_('Back to home')}} + {{_('Back to home')}}
    diff --git a/cps/templates/languages.html b/cps/templates/languages.html index 4c582d27..548abaec 100644 --- a/cps/templates/languages.html +++ b/cps/templates/languages.html @@ -10,7 +10,7 @@ {% endif %}
    {{lang_counter[loop.index0].bookcount}}
    - +
    {% endfor %}
    diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 438e713b..ef83e842 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -12,6 +12,7 @@ + {% if g.current_theme == 1 %} {% endif %} @@ -59,7 +60,7 @@ {% if g.user.role_upload() or g.user.role_admin()%} {% if g.allow_upload %}
  • - +
    {{_('Upload')}}
    @@ -103,14 +104,6 @@
  • {%endif%} {% endfor %} -
    {% if g.user.is_authenticated or g.user.is_anonymous %} @@ -240,6 +233,7 @@ + {% if g.current_theme == 1 %} @@ -247,12 +241,12 @@ {% endif %} {% block js %}{% endblock %} diff --git a/cps/templates/listenmp3.html b/cps/templates/listenmp3.html index e7ec1da6..3ecf3e27 100644 --- a/cps/templates/listenmp3.html +++ b/cps/templates/listenmp3.html @@ -36,7 +36,7 @@
    {% if entry.has_cover %} - {{ entry.title }} {% else %} + {{ entry.title }} {% else %} {{ entry.title }} {% endif %}
    @@ -117,7 +117,7 @@
    • - +
    @@ -129,7 +129,7 @@ filePath: "{{ url_for('static', filename='js/libs/') }}", cssPath: "{{ url_for('static', filename='css/') }}", bookUrl: "{{ url_for('static', filename=mp3file) }}/", - bookmarkUrl: "{{ url_for('bookmark', book_id=mp3file, book_format=audioformat.upper()) }}", + bookmarkUrl: "{{ url_for('web.bookmark', book_id=mp3file, book_format=audioformat.upper()) }}", bookmark: "{{ bookmark.bookmark_key if bookmark != None }}", useBookmarks: "{{ g.user.is_authenticated | tojson }}" }; @@ -137,4 +137,4 @@
    - \ No newline at end of file + diff --git a/cps/templates/login.html b/cps/templates/login.html index 8e622079..fcb6f269 100644 --- a/cps/templates/login.html +++ b/cps/templates/login.html @@ -19,17 +19,17 @@
    {% if config.config_remote_login %} - {{_('Log in with magic link')}} + {{_('Log in with magic link')}} {% endif %} {% if config.config_use_github_oauth %} - + {% endif %} {% if config.config_use_google_oauth %} - + document.onreadystatechange = function () { if (document.readyState == "complete") { - init("{{ url_for('serve_book', book_id=comicfile, book_format=extension) }}"); + init("{{ url_for('web.serve_book', book_id=comicfile, book_format=extension) }}"); } }; diff --git a/cps/templates/readpdf.html b/cps/templates/readpdf.html index 44955bf4..79a8c816 100644 --- a/cps/templates/readpdf.html +++ b/cps/templates/readpdf.html @@ -44,7 +44,7 @@ See https://github.com/adobe-type-tools/cmap-resources - \ No newline at end of file + diff --git a/cps/templates/register.html b/cps/templates/register.html index 24edc3a2..90ba8f8e 100644 --- a/cps/templates/register.html +++ b/cps/templates/register.html @@ -13,14 +13,14 @@ {% if config.config_use_github_oauth %} - + {% endif %} {% if config.config_use_google_oauth %} - + {% for shelf in g.user.shelf %} {% if shelf.is_public != 1 %} -
  • {{shelf.name}}
  • +
  • {{shelf.name}}
  • {% endif %} {% endfor %} {% for shelf in g.public_shelfes %} -
  • {{shelf.name}}
  • +
  • {{shelf.name}}
  • {% endfor %} diff --git a/cps/templates/shelf_order.html b/cps/templates/shelf_order.html index d4d44a62..e3340993 100644 --- a/cps/templates/shelf_order.html +++ b/cps/templates/shelf_order.html @@ -8,8 +8,8 @@
    {{entry.title}}
    {% endfor %} - - {{_('Back')}} + + {{_('Back')}} {% endblock %} diff --git a/cps/templates/shelfdown.html b/cps/templates/shelfdown.html index 32fa78b2..119330a7 100644 --- a/cps/templates/shelfdown.html +++ b/cps/templates/shelfdown.html @@ -32,28 +32,28 @@ {% for entry in entries %}
    - +

    {{entry.title|shortentitle}}

    {% for author in entry.authors %} - {{author.name.replace('|',',')}} + {{author.name.replace('|',',')}} {% if not loop.last %} & {% endif %} {% endfor %}

    - +
    - +
    {% if g.user.role_download() %} {% if entry.data|length %}
    {% if entry.data|length < 2 %} - + {% for format in entry.data %} - + {{format.format}} ({{ format.uncompressed_size|filesizeformat }}) {% endfor %} @@ -79,4 +79,4 @@ {% endblock %} - \ No newline at end of file + diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index 6c57915d..386193bb 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -15,7 +15,7 @@
    {% if ( g.user and g.user.role_passwd() or g.user.role_admin() ) and not content.role_anonymous() %} {% if g.user and g.user.role_admin() and g.allow_registration and not new_user and not profile %} - + {% else %}
    @@ -161,7 +161,7 @@
    {% if not profile %} - {{_('Back')}} + {{_('Back')}} {% endif %}
    @@ -171,8 +171,8 @@

    {{_('Recent Downloads')}}

    {% for entry in downloads %} {% endfor %} diff --git a/cps/ub.py b/cps/ub.py index ba69c4ec..fa8a86e5 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -599,6 +599,8 @@ class Config: # everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding # rows with SQL commands def migrate_Database(): + if not engine.dialect.has_table(engine.connect(), "book_read_link"): + ReadBook.__table__.create(bind=engine) if not engine.dialect.has_table(engine.connect(), "bookmark"): Bookmark.__table__.create(bind=engine) if not engine.dialect.has_table(engine.connect(), "registration"): diff --git a/cps/uploader.py b/cps/uploader.py index df516d24..8dcddb8c 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -65,6 +65,7 @@ logger = logging.getLogger("book_formats") try: from wand.image import Image from wand import version as ImageVersion + from wand.exceptions import PolicyError use_generic_pdf_cover = False except (ImportError, RuntimeError) as e: logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e) @@ -160,12 +161,18 @@ def pdf_preview(tmp_file_path, tmp_dir): if use_generic_pdf_cover: return None else: - cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg" - with Image(filename=tmp_file_path + "[0]", resolution=150) as img: - img.compression_quality = 88 - img.save(filename=os.path.join(tmp_dir, cover_file_name)) - return cover_file_name - + try: + cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg" + with Image(filename=tmp_file_path + "[0]", resolution=150) as img: + img.compression_quality = 88 + img.save(filename=os.path.join(tmp_dir, cover_file_name)) + return cover_file_name + except PolicyError as ex: + logger.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex) + return None + except Exception as ex: + logger.warning('Cannot extract cover image, using default: %s', ex) + return None def get_versions(): if not use_generic_pdf_cover: @@ -197,5 +204,5 @@ def upload(uploadfile): md5.update(filename.encode('utf-8')) tmp_file_path = os.path.join(tmp_dir, md5.hexdigest()) uploadfile.save(tmp_file_path) - meta = book_formats.process(tmp_file_path, filename_root, file_extension) + meta = process(tmp_file_path, filename_root, file_extension) return meta diff --git a/cps/web.py b/cps/web.py index 346cb005..0a4a6e9d 100644 --- a/cps/web.py +++ b/cps/web.py @@ -31,7 +31,6 @@ import os from sqlalchemy.exc import IntegrityError from flask_login import login_user, logout_user, login_required, current_user from flask_babel import gettext as _ - from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.datastructures import Headers from babel import Locale as LC @@ -50,6 +49,7 @@ import gdriveutils from redirect import redirect_back from cps import lm, babel, ub, config, get_locale, language_table, app from pagination import Pagination +# from admin import check_valid_domain # from oauth_bb import oauth_check, register_user_with_oauth try: @@ -280,6 +280,15 @@ def speaking_language(languages=None): lang.name = _(isoLanguages.get(part3=lang.lang_code).name) return languages +# checks if domain is in database (including wildcards) +# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name; +# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/ +def check_valid_domain(domain_text): + domain_text = domain_text.split('@', 1)[-1].lower() + sql = "SELECT * FROM registration WHERE :domain LIKE domain;" + result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all() + return len(result) + # Orders all Authors in the list according to authors sort def order_authors(entry): @@ -359,72 +368,6 @@ def get_email_status_json(): return response -# checks if domain is in database (including wildcards) -# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name; -# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/ -def check_valid_domain(domain_text): - domain_text = domain_text.split('@', 1)[-1].lower() - sql = "SELECT * FROM registration WHERE :domain LIKE domain;" - result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all() - return len(result) - - -@web.route("/ajax/editdomain", methods=['POST']) -@login_required -@admin_required -def edit_domain(): - # POST /post - # name: 'username', //name of field (column in db) - # pk: 1 //primary key (record id) - # value: 'superuser!' //new value - vals = request.form.to_dict() - answer = ub.session.query(ub.Registration).filter(ub.Registration.id == vals['pk']).first() - # domain_name = request.args.get('domain') - answer.domain = vals['value'].replace('*', '%').replace('?', '_').lower() - ub.session.commit() - return "" - - -@web.route("/ajax/adddomain", methods=['POST']) -@login_required -@admin_required -def add_domain(): - domain_name = request.form.to_dict()['domainname'].replace('*', '%').replace('?', '_').lower() - check = ub.session.query(ub.Registration).filter(ub.Registration.domain == domain_name).first() - if not check: - new_domain = ub.Registration(domain=domain_name) - ub.session.add(new_domain) - ub.session.commit() - return "" - - -@web.route("/ajax/deletedomain", methods=['POST']) -@login_required -@admin_required -def delete_domain(): - domain_id = request.form.to_dict()['domainid'].replace('*', '%').replace('?', '_').lower() - ub.session.query(ub.Registration).filter(ub.Registration.id == domain_id).delete() - ub.session.commit() - # If last domain was deleted, add all domains by default - if not ub.session.query(ub.Registration).count(): - new_domain = ub.Registration(domain="%.%") - ub.session.add(new_domain) - ub.session.commit() - return "" - - -@web.route("/ajax/domainlist") -@login_required -@admin_required -def list_domain(): - answer = ub.session.query(ub.Registration).all() - json_dumps = json.dumps([{"domain": r.domain.replace('%', '*').replace('_', '?'), "id": r.id} for r in answer]) - js = json.dumps(json_dumps.replace('"', "'")).lstrip('"').strip('"') - response = make_response(js.replace("'", '"')) - response.headers["Content-Type"] = "application/json; charset=utf-8" - return response - - ''' @web.route("/ajax/getcomic///") @login_required diff --git a/optional-requirements-ldap.txt b/optional-requirements-ldap.txt deleted file mode 100644 index 98519145..00000000 --- a/optional-requirements-ldap.txt +++ /dev/null @@ -1 +0,0 @@ -python_ldap>=3.0.0 \ No newline at end of file diff --git a/optional-requirements.txt b/optional-requirements.txt index 154612b3..399acec4 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -14,6 +14,8 @@ six==1.10.0 # goodreads goodreads>=0.3.2 python-Levenshtein>=0.12.0 +# ldap login +python_ldap>=3.0.0 # other lxml>=3.8.0 rarfile>=2.7