From 8acd1f1fe45b1b4ee9b9487a0261fc196b2dce97 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 4 Apr 2021 19:40:34 +0200 Subject: [PATCH] Code refactoring and improved error handling for edit user list Update teststatus --- cps/admin.py | 242 +++++---- cps/comic.py | 4 +- cps/db.py | 12 +- cps/editbooks.py | 14 +- cps/gdrive.py | 4 +- cps/gdriveutils.py | 8 +- cps/helper.py | 31 +- cps/kobo.py | 4 +- cps/oauth_bb.py | 8 +- cps/services/worker.py | 6 +- cps/shelf.py | 8 +- cps/static/js/table.js | 20 +- cps/tasks/convert.py | 4 +- cps/tasks/mail.py | 6 +- cps/templates/user_table.html | 4 +- cps/uploader.py | 8 +- cps/web.py | 158 +++--- test/Calibre-Web TestSummary_Linux.html | 621 ++++++++++++++---------- 18 files changed, 624 insertions(+), 538 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 66c26418..1d4b5a84 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -42,7 +42,8 @@ from sqlalchemy.sql.expression import func, or_ from . import constants, logger, helper, services from .cli import filepicker from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils -from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash +from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ + valid_email, check_username from .gdriveutils import is_gdrive_ready, gdrive_support from .render_template import render_title_template, get_sidebar_config from . import debug_info @@ -151,7 +152,7 @@ def shutdown(): else: showtext['text'] = _(u'Performing shutdown of server, please close window') # stop gevent/tornado server - web_server.stop(task == 0) + web_server.stop(task==0) return json.dumps(showtext) if task == 2: @@ -241,9 +242,8 @@ def edit_user_table(): @admin_required def list_users(): off = request.args.get("offset") or 0 - limit = request.args.get("limit") or 40 + limit = request.args.get("limit") or 10 search = request.args.get("search") - all_user = ub.session.query(ub.User) if not config.config_anonbrowse: all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) @@ -259,22 +259,13 @@ def list_users(): filtered_count = total_count for user in users: - # set readable locale - #try: - # user.local = LC.parse(user.locale).get_language_name(get_locale()) - #except UnknownLocaleError: - # # This should not happen - # user.local = _(isoLanguages.get(part1=user.locale).name) - # Set default language if user.default_language == "all": user.default = _("all") else: user.default = LC.parse(user.default_language).get_language_name(get_locale()) table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": users} - js_list = json.dumps(table_entries, cls=db.AlchemyEncoder) - response = make_response(js_list) response.headers["Content-Type"] = "application/json; charset=utf-8" return response @@ -294,7 +285,7 @@ def table_get_locale(): ret = list() current_locale = get_locale() for loc in locale: - ret.append({'value':str(loc),'text':loc.get_language_name(current_locale)}) + ret.append({'value': str(loc), 'text': loc.get_language_name(current_locale)}) return json.dumps(ret) @@ -306,7 +297,7 @@ def table_get_default_lang(): ret = list() ret.append({'value':'all','text':_('Show All')}) for lang in languages: - ret.append({'value':lang.lang_code,'text': lang.name}) + ret.append({'value': lang.lang_code, 'text': lang.name}) return json.dumps(ret) @@ -333,43 +324,45 @@ def edit_list_user(param): else: return "" for user in users: - if param =='name': - if not ub.session.query(ub.User).filter(ub.User.name == vals['value']).scalar(): - user.name = vals['value'] - else: - log.error(u"This username is already taken") - return _(u"This username is already taken"), 400 - elif param =='email': - existing_email = ub.session.query(ub.User).filter(ub.User.email == vals['value'].lower()).first() - if not existing_email: - user.email = vals['value'] - else: - log.error(u"Found an existing account for this e-mail address.") - return _(u"Found an existing account for this e-mail address."), 400 - elif param == 'kindle_mail': - user.kindle_mail = vals['value'] - elif param == 'role': - if vals['value'] == 'true': - user.role |= int(vals['field_index']) - else: - user.role &= ~int(vals['field_index']) - elif param == 'sidebar_view': - if vals['value'] == 'true': - user.sidebar_view |= int(vals['field_index']) - else: - user.sidebar_view &= ~int(vals['field_index']) - elif param == 'denied_tags': - user.denied_tags = vals['value'] - elif param == 'allowed_tags': - user.allowed_tags = vals['value'] - elif param == 'allowed_column_value': - user.allowed_column_value = vals['value'] - elif param == 'denied_column_value': - user.denied_column_value = vals['value'] - elif param == 'locale': - user.locale = vals['value'] - elif param == 'default_language': - user.default_language = vals['value'] + try: + vals['value'] = vals['value'].strip() + if param == 'name': + if user.name == "Guest": + raise Exception(_("Guest Name can't be changed")) + user.name = check_username(vals['value']) + elif param =='email': + user.email = check_email(vals['value']) + elif param == 'kindle_mail': + user.kindle_mail = valid_email(vals['value']) if vals['value'] else "" + elif param == 'role': + if vals['value'] == 'true': + user.role |= int(vals['field_index']) + else: + if int(vals['field_index']) == constants.ROLE_ADMIN: + if not ub.session.query(ub.User).\ + filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, + ub.User.id != user.id).count(): + return _(u"No admin user remaining, can't remove admin role", nick=user.name), 400 + user.role &= ~int(vals['field_index']) + elif param == 'sidebar_view': + if vals['value'] == 'true': + user.sidebar_view |= int(vals['field_index']) + else: + user.sidebar_view &= ~int(vals['field_index']) + elif param == 'denied_tags': + user.denied_tags = vals['value'] + elif param == 'allowed_tags': + user.allowed_tags = vals['value'] + elif param == 'allowed_column_value': + user.allowed_column_value = vals['value'] + elif param == 'denied_column_value': + user.denied_column_value = vals['value'] + elif param == 'locale': + user.locale = vals['value'] + elif param == 'default_language': + user.default_language = vals['value'] + except Exception as ex: + return str(ex), 400 ub.session_commit() return "" @@ -378,7 +371,6 @@ def edit_list_user(param): @login_required @admin_required def update_table_settings(): - # ToDo: Save table settings current_user.view_settings['useredit'] = json.loads(request.data) try: try: @@ -387,7 +379,7 @@ def update_table_settings(): pass ub.session.commit() except (InvalidRequestError, OperationalError): - log.error("Invalid request received: %r ", request, ) + log.error("Invalid request received: {}".format(request)) return "Invalid request", 400 return "" @@ -787,7 +779,6 @@ def pathchooser(): folders = [] files = [] - # locale = get_locale() for f in folders: try: data = {"name": f, "fullpath": os.path.join(cwd, f)} @@ -932,7 +923,7 @@ def _configuration_ldap_helper(to_save, gdrive_error): reboot_required |= _config_string(to_save, "config_ldap_cert_path") reboot_required |= _config_string(to_save, "config_ldap_key_path") _config_string(to_save, "config_ldap_group_name") - if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"] != "": + if to_save.get("config_ldap_serv_password", "") != "": reboot_required |= 1 config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8') config.save() @@ -970,7 +961,7 @@ def _configuration_ldap_helper(to_save, gdrive_error): return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'), gdrive_error) - if "ldap_import_user_filter" in to_save and to_save["ldap_import_user_filter"] == '0': + if to_save.get("ldap_import_user_filter") == '0': config.config_ldap_member_user_object = "" else: if config.config_ldap_member_user_object.count("%s") != 1: @@ -1095,8 +1086,8 @@ def _configuration_update_helper(configured): if config.config_use_google_drive and is_gdrive_ready() and not os.path.exists(metadata_db): gdriveutils.downloadFile(None, "metadata.db", metadata_db) db_change = True - except Exception as e: - return _configuration_result('%s' % e, gdrive_error, configured) + except Exception as ex: + return _configuration_result('%s' % ex, gdrive_error, configured) if db_change: if not calibre_db.setup_db(config, ub.app_DB_path): @@ -1148,7 +1139,6 @@ def _configuration_result(error_flash=None, gdrive_error=None, configured=True): def _handle_new_user(to_save, content, languages, translations, kobo_support): content.default_language = to_save["default_language"] - # content.mature_content = "Show_mature_content" in to_save content.locale = to_save.get("locale", content.locale) content.sidebar_view = sum(int(key[5:]) for key in to_save if key.startswith('show_')) @@ -1156,28 +1146,21 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): content.sidebar_view |= constants.DETAIL_RANDOM content.role = constants.selected_roles(to_save) - - if not to_save["name"] or not to_save["email"] or not to_save["password"]: - flash(_(u"Please fill out all fields!"), category="error") - return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, - registered_oauth=oauth_check, kobo_support=kobo_support, - title=_(u"Add new user")) content.password = generate_password_hash(to_save["password"]) - existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == to_save["name"].lower()) \ - .first() - existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \ - .first() - if not existing_user and not existing_email: - content.name = to_save["name"] - if config.config_public_reg and not check_valid_domain(to_save["email"]): - flash(_(u"E-mail is not from valid domain"), category="error") - return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, - registered_oauth=oauth_check, kobo_support=kobo_support, - title=_(u"Add new user")) - else: - content.email = to_save["email"] - else: - flash(_(u"Found an existing account for this e-mail address or name."), category="error") + try: + if not to_save["name"] or not to_save["email"] or not to_save["password"]: + log.info("Missing entries on new user") + raise Exception(_(u"Please fill out all fields!")) + content.email = check_email(to_save["email"]) + # Query User name, if not existing, change + content.name = check_username(to_save["name"]) + if to_save.get("kindle_mail"): + content.kindle_mail = valid_email(to_save["kindle_mail"]) + if config.config_public_reg and not check_valid_domain(content.email): + log.info("E-mail: {} for new user is not from valid domain".format(content.email)) + raise Exception(_(u"E-mail is not from valid domain")) + except Exception as ex: + flash(str(ex), category="error") return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, languages=languages, title=_(u"Add new user"), page="newuser", kobo_support=kobo_support, registered_oauth=oauth_check) @@ -1199,7 +1182,7 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): def _handle_edit_user(to_save, content, languages, translations, kobo_support): - if "delete" in to_save: + if to_save.get("delete"): if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, ub.User.id != content.id).count(): ub.session.query(ub.User).filter(ub.User.id == content.id).delete() @@ -1214,8 +1197,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): ub.User.id != content.id).count() and 'admin_role' not in to_save: flash(_(u"No admin user remaining, can't remove admin role", nick=content.name), category="error") return redirect(url_for('admin.admin')) - - if "password" in to_save and to_save["password"]: + if to_save.get("password"): content.password = generate_password_hash(to_save["password"]) anonymous = content.is_anonymous content.role = constants.selected_roles(to_save) @@ -1233,49 +1215,37 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): elif value not in val and content.check_visibility(value): content.sidebar_view &= ~value - if "Show_detail_random" in to_save: + if to_save.get("Show_detail_random"): content.sidebar_view |= constants.DETAIL_RANDOM else: content.sidebar_view &= ~constants.DETAIL_RANDOM - if "default_language" in to_save: + if to_save.get("default_language"): content.default_language = to_save["default_language"] - if "locale" in to_save and to_save["locale"]: + if to_save.get("locale"): content.locale = to_save["locale"] - if to_save["email"] and to_save["email"] != content.email: - existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \ - .first() - if not existing_email: - content.email = to_save["email"] - else: - flash(_(u"Found an existing account for this e-mail address."), category="error") - return render_title_template("user_edit.html", - translations=translations, - languages=languages, - mail_configured=config.get_mail_server_configured(), - kobo_support=kobo_support, - new_user=0, - content=content, - registered_oauth=oauth_check, - title=_(u"Edit User %(nick)s", nick=content.name), page="edituser") - if "name" in to_save and to_save["name"] != content.name: + try: + if to_save.get("email", content.email) != content.email: + content.email = check_email(to_save["email"]) # Query User name, if not existing, change - if not ub.session.query(ub.User).filter(ub.User.name == to_save["name"]).scalar(): - content.name = to_save["name"] - else: - flash(_(u"This username is already taken"), category="error") - return render_title_template("user_edit.html", - translations=translations, - languages=languages, - mail_configured=config.get_mail_server_configured(), - new_user=0, content=content, - registered_oauth=oauth_check, - kobo_support=kobo_support, - title=_(u"Edit User %(nick)s", nick=content.name), - page="edituser") - - if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail: - content.kindle_mail = to_save["kindle_mail"] + if to_save.get("name", content.name) != content.name: + if to_save.get("name") == "Guest": + raise Exception(_("Guest Name can't be changed")) + content.name = check_username(to_save["name"]) + if to_save.get("kindle_mail") != content.kindle_mail: + content.kindle_mail = valid_email(to_save["kindle_mail"]) if to_save["kindle_mail"] else "" + except Exception as ex: + flash(str(ex), category="error") + return render_title_template("user_edit.html", + translations=translations, + languages=languages, + mail_configured=config.get_mail_server_configured(), + kobo_support=kobo_support, + new_user=0, + content=content, + registered_oauth=oauth_check, + title=_(u"Edit User %(nick)s", nick=content.name), + page="edituser") try: ub.session_commit() flash(_(u"User '%(nick)s' updated", nick=content.name), category="success") @@ -1330,10 +1300,10 @@ def update_mailsettings(): elif to_save.get("gmail"): try: config.mail_gmail_token = services.gmail.setup_gmail(config.mail_gmail_token) - flash(_(u"G-Mail Account Verification Successfull"), category="success") - except Exception as e: - flash(e, category="error") - log.error(e) + flash(_(u"G-Mail Account Verification Successful"), category="success") + except Exception as ex: + flash(str(ex), category="error") + log.error(ex) return edit_mailsettings() else: @@ -1389,7 +1359,8 @@ def edit_user(user_id): registered_oauth=oauth_check, mail_configured=config.get_mail_server_configured(), kobo_support=kobo_support, - title=_(u"Edit User %(nick)s", nick=content.name), page="edituser") + title=_(u"Edit User %(nick)s", nick=content.name), + page="edituser") @admi.route("/admin/resetpassword/") @@ -1530,9 +1501,12 @@ def ldap_import_create_user(user, user_data): else: log.debug('No Mail Field Found in LDAP Response') useremail = username + '@email.com' - # check for duplicate email - if ub.session.query(ub.User).filter(func.lower(ub.User.email) == useremail.lower()).first(): - log.warning("LDAP Email %s Already in Database", user_data) + + try: + # check for duplicate email + useremail = check_email(useremail) + except Exception as ex: + log.warning("LDAP Email Error: {}, {}".format(user_data, ex)) return 0, None content = ub.User() content.name = username @@ -1549,8 +1523,8 @@ def ldap_import_create_user(user, user_data): try: ub.session.commit() return 1, None # increase no of users - except Exception as e: - log.warning("Failed to create LDAP user: %s - %s", user, e) + except Exception as ex: + log.warning("Failed to create LDAP user: %s - %s", user, ex) ub.session.rollback() message = _(u'Failed to Create at Least One LDAP User') return 0, message @@ -1583,16 +1557,16 @@ def import_ldap_users(): query_filter = config.config_ldap_user_object try: user_identifier = extract_user_identifier(user, query_filter) - except Exception as e: - log.warning(e) + except Exception as ex: + log.warning(ex) continue else: user_identifier = user query_filter = None try: user_data = services.ldap.get_object_details(user=user_identifier, query_filter=query_filter) - except AttributeError as e: - log.debug_or_exception(e) + except AttributeError as ex: + log.debug_or_exception(ex) continue if user_data: user_count, message = ldap_import_create_user(user, user_data) diff --git a/cps/comic.py b/cps/comic.py index c1f1fd63..462c11f0 100644 --- a/cps/comic.py +++ b/cps/comic.py @@ -105,8 +105,8 @@ def _extract_Cover_from_archive(original_file_extension, tmp_file_name, rarExecu if extension in COVER_EXTENSIONS: cover_data = cf.read(name) break - except Exception as e: - log.debug('Rarfile failed with error: %s', e) + except Exception as ex: + log.debug('Rarfile failed with error: %s', ex) return cover_data diff --git a/cps/db.py b/cps/db.py index 6e5dfbbf..5cb04ed3 100644 --- a/cps/db.py +++ b/cps/db.py @@ -544,8 +544,8 @@ class CalibreDB(): conn = cls.engine.connect() # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 - except Exception as e: - config.invalidate(e) + except Exception as ex: + config.invalidate(ex) return False config.db_configured = True @@ -646,8 +646,8 @@ class CalibreDB(): pagination = Pagination(page, pagesize, len(query.all())) entries = query.order_by(*order).offset(off).limit(pagesize).all() - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) #for book in entries: # book = self.order_authors(book) return entries, randm, pagination @@ -791,7 +791,7 @@ class CalibreDB(): def lcase(s): try: return unidecode.unidecode(s.lower()) - except Exception as e: + except Exception as ex: log = logger.create() - log.debug_or_exception(e) + log.debug_or_exception(ex) return s.lower() diff --git a/cps/editbooks.py b/cps/editbooks.py index 5fedee50..cb8388ef 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -336,8 +336,8 @@ def delete_book(book_id, book_format, jsonResponse): calibre_db.session.query(db.Data).filter(db.Data.book == book.id).\ filter(db.Data.format == book_format).delete() calibre_db.session.commit() - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) calibre_db.session.rollback() else: # book not found @@ -726,10 +726,10 @@ def edit_book(book_id): edited_books_id = None # handle book title - modif_date |= handle_title_on_edit(book, to_save["book_title"]) + title_change = handle_title_on_edit(book, to_save["book_title"]) - input_authors, change = handle_author_on_edit(book, to_save["author_name"]) - if change: + input_authors, authorchange = handle_author_on_edit(book, to_save["author_name"]) + if authorchange or title_change: edited_books_id = book.id modif_date = True @@ -801,8 +801,8 @@ def edit_book(book_id): calibre_db.session.rollback() flash(error, category="error") return render_edit_book(book_id) - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) calibre_db.session.rollback() flash(_("Error editing book, please check logfile for details"), category="error") return redirect(url_for('web.show_book', book_id=book.id)) diff --git a/cps/gdrive.py b/cps/gdrive.py index 158a5a4f..6d6dfd07 100644 --- a/cps/gdrive.py +++ b/cps/gdrive.py @@ -155,6 +155,6 @@ def on_received_watch_confirmation(): # prevent error on windows, as os.rename does on existing files, also allow cross hdd move move(os.path.join(tmp_dir, "tmp_metadata.db"), dbpath) calibre_db.reconnect_db(config, ub.app_DB_path) - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) return '' diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index 5f970554..a98d0b66 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -202,8 +202,8 @@ def getDrive(drive=None, gauth=None): gauth.Refresh() except RefreshError as e: log.error("Google Drive error: %s", e) - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) else: # Initialize the saved creds gauth.Authorize() @@ -497,8 +497,8 @@ def getChangeById (drive, change_id): except (errors.HttpError) as error: log.error(error) return None - except Exception as e: - log.error(e) + except Exception as ex: + log.error(ex) return None diff --git a/cps/helper.py b/cps/helper.py index f5e57e02..f1c32ea0 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -35,7 +35,7 @@ from babel.units import format_unit from flask import send_from_directory, make_response, redirect, abort, url_for from flask_babel import gettext as _ from flask_login import current_user -from sqlalchemy.sql.expression import true, false, and_, text +from sqlalchemy.sql.expression import true, false, and_, text, func from werkzeug.datastructures import Headers from werkzeug.security import generate_password_hash @@ -504,6 +504,31 @@ def uniq(inpt): output.append(x) return output +def check_email(email): + email = valid_email(email) + if ub.session.query(ub.User).filter(func.lower(ub.User.email) == email.lower()).first(): + log.error(u"Found an existing account for this e-mail address") + raise Exception(_(u"Found an existing account for this e-mail address")) + return email + + +def check_username(username): + username = username.strip() + if ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).scalar(): + log.error(u"This username is already taken") + raise Exception (_(u"This username is already taken")) + return username + + +def valid_email(email): + email = email.strip() + # Regex according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation + if not re.search(r"^[\w.!#$%&'*+\\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$", + email): + log.error(u"Invalid e-mail address format") + raise Exception(_(u"Invalid e-mail address format")) + return email + # ################################# External interface ################################# @@ -551,8 +576,8 @@ def get_book_cover_internal(book, use_generic_cover_on_failure): else: log.error('%s/cover.jpg not found on Google Drive', book.path) return get_cover_on_failure(use_generic_cover_on_failure) - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) return get_cover_on_failure(use_generic_cover_on_failure) else: cover_file_path = os.path.join(config.config_calibre_dir, book.path) diff --git a/cps/kobo.py b/cps/kobo.py index e99df9b3..8988ef3f 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -262,8 +262,8 @@ def generate_sync_response(sync_token, sync_results, set_cont=False): extra_headers["x-kobo-sync-mode"] = store_response.headers.get("x-kobo-sync-mode") extra_headers["x-kobo-recent-reads"] = store_response.headers.get("x-kobo-recent-reads") - except Exception as e: - log.error("Failed to receive or parse response from Kobo's sync endpoint: " + str(e)) + except Exception as ex: + log.error("Failed to receive or parse response from Kobo's sync endpoint: {}".format(ex)) if set_cont: extra_headers["x-kobo-sync"] = "continue" sync_token.to_headers(extra_headers) diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py index faa3f8fd..5d909d91 100644 --- a/cps/oauth_bb.py +++ b/cps/oauth_bb.py @@ -147,8 +147,8 @@ def bind_oauth_or_register(provider_id, provider_user_id, redirect_url, provider ub.session.commit() flash(_(u"Link to %(oauth)s Succeeded", oauth=provider_name), category="success") return redirect(url_for('web.profile')) - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) ub.session.rollback() else: flash(_(u"Login failed, No User Linked With OAuth Account"), category="error") @@ -194,8 +194,8 @@ def unlink_oauth(provider): ub.session.commit() logout_oauth_user() flash(_(u"Unlink to %(oauth)s Succeeded", oauth=oauth_check[provider]), category="success") - except Exception as e: - log.debug_or_exception(e) + except Exception as ex: + log.debug_or_exception(ex) ub.session.rollback() flash(_(u"Unlink to %(oauth)s Failed", oauth=oauth_check[provider]), category="error") except NoResultFound: diff --git a/cps/services/worker.py b/cps/services/worker.py index 072674a0..8433e408 100644 --- a/cps/services/worker.py +++ b/cps/services/worker.py @@ -159,9 +159,9 @@ class CalibreTask: # catch any unhandled exceptions in a task and automatically fail it try: self.run(*args) - except Exception as e: - self._handleError(str(e)) - log.debug_or_exception(e) + except Exception as ex: + self._handleError(str(ex)) + log.debug_or_exception(ex) self.end_time = datetime.now() diff --git a/cps/shelf.py b/cps/shelf.py index 5c6037ac..68e8331b 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -255,13 +255,13 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): log.info(u"Shelf {} {}".format(to_save["title"], shelf_action)) flash(flash_text, category="success") return redirect(url_for('shelf.show_shelf', shelf_id=shelf.id)) - except (OperationalError, InvalidRequestError) as e: + except (OperationalError, InvalidRequestError) as ex: ub.session.rollback() - log.debug_or_exception(e) + log.debug_or_exception(ex) flash(_(u"Settings DB is not Writeable"), category="error") - except Exception as e: + except Exception as ex: ub.session.rollback() - log.debug_or_exception(e) + log.debug_or_exception(ex) flash(_(u"There was an error"), category="error") return render_title_template('shelf_edit.html', shelf=shelf, title=title, page=page) diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 6eed51f0..96901d0b 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -452,6 +452,17 @@ $(function() { $(this).next().text(elText); }); }, + onLoadSuccess: function () { + var guest = $(".editable[data-name='name'][data-value='Guest']"); + guest.editable("disable"); + $(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").editable("disable"); + $("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); + $("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); + $("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); + // ToDo: Disable delete + + }, + // eslint-disable-next-line no-unused-vars /*onEditableSave: function (field, row, oldvalue, $el) { if (field === "title" || field === "authors") { @@ -612,9 +623,9 @@ function singleUserFormatter(value, row) { function checkboxFormatter(value, row, index){ if(value & this.column) - return ''; + return ''; else - return ''; + return ''; } function checkboxChange(checkbox, userId, field, field_index) { @@ -622,6 +633,11 @@ function checkboxChange(checkbox, userId, field, field_index) { method:"post", url: window.location.pathname + "/../../ajax/editlistusers/" + field, data: {"pk":userId, "field_index":field_index, "value": checkbox.checked} + /*
+ +
*/ + /*
Text to show
*/ }); $.ajax({ method:"get", diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index d3e74569..2e2a51d8 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -75,8 +75,8 @@ class TaskConvert(CalibreTask): self.settings['body'], internal=True) ) - except Exception as e: - return self._handleError(str(e)) + except Exception as ex: + return self._handleError(str(ex)) def _convert_ebook_format(self): error_message = None diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 89e9e2f0..8d2d4188 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -158,9 +158,9 @@ class TaskEmail(CalibreTask): except socket.error as e: log.debug_or_exception(e) self._handleError(u'Socket Error sending email: {}'.format(e.strerror)) - except Exception as e: - log.debug_or_exception(e) - self._handleError(u'Error sending email: {}'.format(e)) + except Exception as ex: + log.debug_or_exception(ex) + self._handleError(u'Error sending email: {}'.format(ex)) def send_standard_email(self, msg): diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index 318f873a..e7ddf156 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -43,6 +43,7 @@ data-visible="{{visiblility.get(parameter)}}" data-editable-type="select" data-edit="true" + data-sortable="true" data-editable-url="{{ url_for('admin.edit_list_user', param=parameter)}}" data-editable-source={{url}} {% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}> @@ -65,6 +66,7 @@ data-visible="{{visiblility.get(parameter)}}" data-editable-type="select" data-edit="true" + data-sortable="true" data-editable-url="{{ url_for('admin.edit_list_user', param=parameter)}}" data-editable-source={{url}} {% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}> @@ -101,7 +103,7 @@ {{ user_table_row('name', _('Enter Username'), _('Username'), true) }} {{ user_table_row('email', _('Enter E-mail Address'), _('E-mail Address'), true) }} - {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), true) }} + {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), false) }} {{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }} {{ user_select_languages('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }} {{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), false, true, 0) }} diff --git a/cps/uploader.py b/cps/uploader.py index 82caf308..0d59fd01 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -117,8 +117,8 @@ def parse_xmp(pdf_file): """ try: xmp_info = pdf_file.getXmpMetadata() - except Exception as e: - log.debug('Can not read XMP metadata %e', e) + except Exception as ex: + log.debug('Can not read XMP metadata {}'.format(ex)) return None if xmp_info: @@ -162,8 +162,8 @@ def parse_xmp(pdf_file): """ try: xmp_info = pdf_file.getXmpMetadata() - except Exception as e: - log.debug('Can not read XMP metadata', e) + except Exception as ex: + log.debug('Can not read XMP metadata {}'.format(ex)) return None if xmp_info: diff --git a/cps/web.py b/cps/web.py index 0eff88f1..e1acdcef 100644 --- a/cps/web.py +++ b/cps/web.py @@ -24,7 +24,6 @@ from __future__ import division, print_function, unicode_literals import os from datetime import datetime import json -import re import mimetypes import chardet # dependency of requests @@ -50,9 +49,9 @@ from . import constants, logger, isoLanguages, services from . import babel, db, ub, config, get_locale, app from . import calibre_db from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download -from .helper import check_valid_domain, render_task_status, \ +from .helper import check_valid_domain, render_task_status, check_email, check_username, \ get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \ - send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password + send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password, valid_email from .pagination import Pagination from .redirect import redirect_back from .usermanagement import login_required_if_no_ano @@ -215,8 +214,8 @@ def update_view(): for element in to_save: for param in to_save[element]: current_user.set_view_property(element, param, to_save[element][param]) - except Exception as e: - log.error("Could not save view_settings: %r %r: %e", request, to_save, e) + except Exception as ex: + log.error("Could not save view_settings: %r %r: %e", request, to_save, ex) return "Invalid request", 400 return "1", 200 @@ -1164,14 +1163,6 @@ def extend_search_term(searchterm, searchterm.extend(tag.name for tag in tag_names) tag_names = calibre_db.session.query(db_element).filter(db.Tags.id.in_(tags['exclude_' + key])).all() searchterm.extend(tag.name for tag in tag_names) - #serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(tags['include_serie'])).all() - #searchterm.extend(serie.name for serie in serie_names) - #serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(tags['include_serie'])).all() - #searchterm.extend(serie.name for serie in serie_names) - #shelf_names = ub.session.query(ub.Shelf).filter(ub.Shelf.id.in_(tags['include_shelf'])).all() - #searchterm.extend(shelf.name for shelf in shelf_names) - #shelf_names = ub.session.query(ub.Shelf).filter(ub.Shelf.id.in_(tags['include_shelf'])).all() - #searchterm.extend(shelf.name for shelf in shelf_names) language_names = calibre_db.session.query(db.Languages). \ filter(db.Languages.id.in_(tags['include_language'])).all() if language_names: @@ -1345,11 +1336,7 @@ def serve_book(book_id, book_format, anyname): @login_required_if_no_ano @download_required def download_link(book_id, book_format, anyname): - if "Kobo" in request.headers.get('User-Agent'): - client = "kobo" - else: - client="" - + client = "kobo" if "Kobo" in request.headers.get('User-Agent') else "" return get_download_link(book_id, book_format, client) @@ -1391,52 +1378,41 @@ def register(): if request.method == "POST": to_save = request.form.to_dict() - if config.config_register_email: - nickname = to_save["email"] - else: - nickname = to_save.get('name', None) - if not nickname or not to_save.get("email", None): + nickname = to_save["email"].strip() if config.config_register_email else to_save.get('name') + if not nickname or not to_save.get("email"): flash(_(u"Please fill out all fields!"), category="error") return render_title_template('register.html', title=_(u"register"), page="register") - #if to_save["email"].count("@") != 1 or not \ - # Regex according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation - if not re.search(r"^[\w.!#$%&'*+\\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$", - to_save["email"]): - flash(_(u"Invalid e-mail address format"), category="error") - log.warning('Registering failed for user "%s" e-mail address: %s', nickname, to_save["email"]) + try: + nickname = check_username(nickname) + email = check_email(to_save["email"]) + except Exception as ex: + flash(str(ex), category="error") return render_title_template('register.html', title=_(u"register"), page="register") - existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == nickname - .lower()).first() - existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()).first() - if not existing_user and not existing_email: - content = ub.User() - if check_valid_domain(to_save["email"]): - content.name = nickname - content.email = to_save["email"] - password = generate_random_password() - content.password = generate_password_hash(password) - content.role = config.config_default_role - content.sidebar_view = config.config_default_show - try: - ub.session.add(content) - ub.session.commit() - if feature_support['oauth']: - register_user_with_oauth(content) - send_registration_mail(to_save["email"], nickname, password) - except Exception: - ub.session.rollback() - flash(_(u"An unknown error occurred. Please try again later."), category="error") - return render_title_template('register.html', title=_(u"register"), page="register") - else: - flash(_(u"Your e-mail is not allowed to register"), category="error") - log.warning('Registering failed for user "%s" e-mail address: %s', nickname, to_save["email"]) + content = ub.User() + if check_valid_domain(email): + content.name = nickname + content.email = email + password = generate_random_password() + content.password = generate_password_hash(password) + content.role = config.config_default_role + content.sidebar_view = config.config_default_show + try: + ub.session.add(content) + ub.session.commit() + if feature_support['oauth']: + register_user_with_oauth(content) + send_registration_mail(to_save["email"].strip(), nickname, password) + except Exception: + ub.session.rollback() + flash(_(u"An unknown error occurred. Please try again later."), category="error") return render_title_template('register.html', title=_(u"register"), page="register") - flash(_(u"Confirmation e-mail was send to your e-mail account."), category="success") - return redirect(url_for('web.login')) else: - flash(_(u"This username or e-mail address is already in use."), category="error") + flash(_(u"Your e-mail is not allowed to register"), category="error") + log.warning('Registering failed for user "%s" e-mail address: %s', nickname, to_save["email"]) return render_title_template('register.html', title=_(u"register"), page="register") + flash(_(u"Confirmation e-mail was send to your e-mail account."), category="success") + return redirect(url_for('web.login')) if feature_support['oauth']: register_user_with_oauth() @@ -1527,63 +1503,41 @@ def logout(): return redirect(url_for('web.login')) - - - # ################################### Users own configuration ######################################################### -def change_profile_email(to_save, kobo_support, local_oauth_check, oauth_status): - if "email" in to_save and to_save["email"] != current_user.email: - if config.config_public_reg and not check_valid_domain(to_save["email"]): - flash(_(u"E-mail is not from valid domain"), category="error") - return render_title_template("user_edit.html", content=current_user, - title=_(u"%(name)s's profile", name=current_user.name), page="me", - kobo_support=kobo_support, - registered_oauth=local_oauth_check, oauth_status=oauth_status) - current_user.email = to_save["email"] - -def change_profile_nickname(to_save, kobo_support, local_oauth_check, translations, languages): - if "name" in to_save and to_save["name"] != current_user.name: - # Query User name, if not existing, change - if not ub.session.query(ub.User).filter(ub.User.name == to_save["name"]).scalar(): - current_user.name = to_save["name"] - else: - flash(_(u"This username is already taken"), category="error") - return render_title_template("user_edit.html", - translations=translations, - languages=languages, - kobo_support=kobo_support, - new_user=0, content=current_user, - registered_oauth=local_oauth_check, - title=_(u"Edit User %(nick)s", - nick=current_user.name), - page="edituser") - - def change_profile(kobo_support, local_oauth_check, oauth_status, translations, languages): to_save = request.form.to_dict() current_user.random_books = 0 if current_user.role_passwd() or current_user.role_admin(): - if "password" in to_save and to_save["password"]: + if to_save.get("password"): current_user.password = generate_password_hash(to_save["password"]) - if "kindle_mail" in to_save and to_save["kindle_mail"] != current_user.kindle_mail: - current_user.kindle_mail = to_save["kindle_mail"] - if "allowed_tags" in to_save and to_save["allowed_tags"] != current_user.allowed_tags: - current_user.allowed_tags = to_save["allowed_tags"].strip() - change_profile_email(to_save, kobo_support, local_oauth_check, oauth_status) - change_profile_nickname(to_save, kobo_support, local_oauth_check, translations, languages) - if "show_random" in to_save and to_save["show_random"] == "on": - current_user.random_books = 1 - if "default_language" in to_save: - current_user.default_language = to_save["default_language"] - if "locale" in to_save: - current_user.locale = to_save["locale"] + try: + if to_save.get("allowed_tags", current_user.allowed_tags) != current_user.allowed_tags: + current_user.allowed_tags = to_save["allowed_tags"].strip() + if to_save.get("kindle_mail", current_user.kindle_mail) != current_user.kindle_mail: + current_user.kindle_mail = valid_email(to_save["kindle_mail"]) + if to_save.get("email", current_user.email) != current_user.email: + current_user.email = check_email(to_save["email"]) + if to_save.get("name", current_user.name) != current_user.name: + # Query User name, if not existing, change + current_user.name = check_username(to_save["name"]) + current_user.random_books = 1 if to_save.get("show_random") == "on" else 0 + if to_save.get("default_language"): + current_user.default_language = to_save["default_language"] + if to_save.get("locale"): + current_user.locale = to_save["locale"] + except Exception as ex: + flash(str(ex), category="error") + return render_title_template("user_edit.html", content=current_user, + title=_(u"%(name)s's profile", name=current_user.name), page="me", + kobo_support=kobo_support, + registered_oauth=local_oauth_check, oauth_status=oauth_status) val = 0 for key, __ in to_save.items(): if key.startswith('show'): val += int(key[5:]) current_user.sidebar_view = val - if "Show_detail_random" in to_save: + if to_save.get("Show_detail_random"): current_user.sidebar_view += constants.DETAIL_RANDOM try: diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 42cb483f..2d41ea3a 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2021-03-28 20:37:06

+

Start Time: 2021-04-05 18:59:35

-

Stop Time: 2021-03-28 23:05:12

+

Stop Time: 2021-04-05 21:34:25

-

Duration: 2h 0 min

+

Duration: 2h 5 min

@@ -500,11 +500,11 @@ - + TestEbookConvertCalibreGDrive 6 - 5 - 1 + 6 + 0 0 0 @@ -532,33 +532,11 @@ - +
TestEbookConvertCalibreGDrive - test_convert_only
- -
- FAIL -
- - - - + PASS @@ -1172,13 +1150,13 @@ AssertionError: 'Failed' != 'Finished' TestEditBooksList - 3 - 3 + 10 + 10 0 0 0 - Detail + Detail @@ -1186,7 +1164,7 @@ AssertionError: 'Failed' != 'Finished' -
TestEditBooksList - test_edit_books_list
+
TestEditBooksList - test_bookslist_edit_author
PASS @@ -1195,7 +1173,7 @@ AssertionError: 'Failed' != 'Finished' -
TestEditBooksList - test_list_visibility
+
TestEditBooksList - test_bookslist_edit_categories
PASS @@ -1204,7 +1182,70 @@ AssertionError: 'Failed' != 'Finished' -
TestEditBooksList - test_merge_book
+
TestEditBooksList - test_bookslist_edit_languages
+ + PASS + + + + + + +
TestEditBooksList - test_bookslist_edit_publisher
+ + PASS + + + + + + +
TestEditBooksList - test_bookslist_edit_series
+ + PASS + + + + + + +
TestEditBooksList - test_bookslist_edit_seriesindex
+ + PASS + + + + + + +
TestEditBooksList - test_bookslist_edit_title
+ + PASS + + + + + + +
TestEditBooksList - test_list_visibility
+ + PASS + + + + + + +
TestEditBooksList - test_restricted_rights
+ + PASS + + + + + + +
TestEditBooksList - test_search_books_list
PASS @@ -1215,8 +1256,8 @@ AssertionError: 'Failed' != 'Finished' TestEditBooksOnGdrive 20 - 19 - 1 + 18 + 2 0 0 @@ -1235,11 +1276,34 @@ AssertionError: 'Failed' != 'Finished' - +
TestEditBooksOnGdrive - test_edit_author
- PASS + +
+ FAIL +
+ + + + @@ -1416,7 +1480,7 @@ AssertionError: 'Failed' != 'Finished'
Traceback (most recent call last):
   File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 848, in test_watch_metadata
     self.assertNotIn('series', book)
-AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': 'testbook', 'author': ['John Döe'], 'rating': 0, 'languages': ['English'], 'identifier': [], 'cover': '/cover/5?edit=4ff3250d-64b1-4c99-8a68-3b7e505dc2f2', 'tag': [], 'publisher': ['Randomhäus'], 'comment': '

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Aenean commodo ligula eget dolor.

Aenean massa.

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.

Nulla consequat massa quis enim.

Donec pede justo, fringilla vel, aliquet nec, vulputate

', 'add_shelf': [], 'del_shelf': [], 'edit_enable': True, 'kindle': None, 'kindlebtn': None, 'download': ['EPUB (6.7 kB)'], 'read': False, 'archived': False, 'series_all': 'Book 1.0 of test', 'series_index': '1.0', 'series': 'test', 'cust_columns': []}
+AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': 'testbook', 'author': ['John Döe'], 'rating': 0, 'languages': ['English'], 'identifier': [], 'cover': '/cover/5?edit=18d56230-76a3-40ad-a368-d1fa776f385a', 'tag': [], 'publisher': ['Randomhäus'], 'comment': '

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Aenean commodo ligula eget dolor.

Aenean massa.

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.

Nulla consequat massa quis enim.

Donec pede justo, fringilla vel, aliquet nec, vulputate

', 'add_shelf': [], 'del_shelf': [], 'edit_enable': True, 'kindle': None, 'kindlebtn': None, 'download': ['EPUB (6.7 kB)'], 'read': False, 'archived': False, 'series_all': 'Book 1.0 of test', 'series_index': '1.0', 'series': 'test', 'cust_columns': []}
@@ -1427,12 +1491,12 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': - + TestSTARTTLS 3 - 2 + 3 + 0 0 - 1 0 Detail @@ -1459,41 +1523,21 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': - +
TestSTARTTLS - test_STARTTLS_resend_password
- -
- ERROR -
- - - - + PASS - + TestSSL 5 - 4 - 1 + 5 + 0 0 0 @@ -1521,31 +1565,11 @@ IndexError: list index out of range - +
TestSSL - test_SSL_logging_email
- -
- FAIL -
- - - - + PASS @@ -1708,10 +1732,8 @@ AssertionError: 0 is not true : Email logging not working
Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/test/test_kobo_sync.py", line 585, in test_book_download
-    data = self.inital_sync()
-  File "/home/ozzie/Development/calibre-web-test/test/test_kobo_sync.py", line 133, in inital_sync
-    self.assertEqual(data[3]['NewEntitlement']['BookMetadata']['DownloadUrls'][1]['Format'], 'EPUB')
+  File "/home/ozzie/Development/calibre-web-test/test/test_kobo_sync.py", line 586, in test_book_download
+    download = downloadSession.get(data[0]['NewEntitlement']['BookMetadata']['DownloadUrls'][1]['Url'], headers=TestKoboSync.header)
 IndexError: list index out of range
@@ -2164,7 +2186,7 @@ IndexError: list index out of range - TestOAuthLogin + TestMergeBooksList 2 2 0 @@ -2179,7 +2201,7 @@ IndexError: list index out of range -
TestOAuthLogin - test_oauth_about
+
TestMergeBooksList - test_delete_book
PASS @@ -2187,6 +2209,39 @@ IndexError: list index out of range + +
TestMergeBooksList - test_merge_book
+ + PASS + + + + + + + TestOAuthLogin + 2 + 2 + 0 + 0 + 0 + + Detail + + + + + + + +
TestOAuthLogin - test_oauth_about
+ + PASS + + + + +
TestOAuthLogin - test_visible_oauth
@@ -2204,13 +2259,13 @@ IndexError: list index out of range 0 0 - Detail + Detail - +
TestOPDSFeed - test_opds
@@ -2219,7 +2274,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_author
@@ -2228,7 +2283,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_books
@@ -2237,7 +2292,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_calibre_companion
@@ -2246,7 +2301,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_cover
@@ -2255,7 +2310,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_download_book
@@ -2264,7 +2319,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_formats
@@ -2273,7 +2328,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_guest_user
@@ -2282,7 +2337,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_hot
@@ -2291,7 +2346,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_language
@@ -2300,7 +2355,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_non_admin
@@ -2309,7 +2364,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_publisher
@@ -2318,7 +2373,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_random
@@ -2327,7 +2382,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_ratings
@@ -2336,7 +2391,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_read_unread
@@ -2345,7 +2400,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_search
@@ -2354,7 +2409,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_series
@@ -2363,7 +2418,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_shelf_access
@@ -2372,7 +2427,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_tags
@@ -2381,7 +2436,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_top_rated
@@ -2390,7 +2445,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_opds_unicode_user
@@ -2399,7 +2454,7 @@ IndexError: list index out of range - +
TestOPDSFeed - test_recently_added
@@ -2417,13 +2472,13 @@ IndexError: list index out of range 0 0 - Detail + Detail - +
TestUploadPDF - test_upload_invalid_pdf
@@ -2441,13 +2496,13 @@ IndexError: list index out of range 0 0 - Detail + Detail - +
TestReader - test_comic_reader
@@ -2456,7 +2511,7 @@ IndexError: list index out of range - +
TestReader - test_epub_reader
@@ -2465,7 +2520,7 @@ IndexError: list index out of range - +
TestReader - test_pdf_reader
@@ -2474,7 +2529,7 @@ IndexError: list index out of range - +
TestReader - test_sound_listener
@@ -2483,7 +2538,7 @@ IndexError: list index out of range - +
TestReader - test_txt_reader
@@ -2501,13 +2556,13 @@ IndexError: list index out of range 0 0 - Detail + Detail - +
TestRegister - test_forgot_password
@@ -2516,7 +2571,7 @@ IndexError: list index out of range - +
TestRegister - test_illegal_email
@@ -2525,7 +2580,7 @@ IndexError: list index out of range - +
TestRegister - test_limit_domain
@@ -2534,7 +2589,7 @@ IndexError: list index out of range - +
TestRegister - test_register_no_server
@@ -2543,7 +2598,7 @@ IndexError: list index out of range - +
TestRegister - test_registering_only_email
@@ -2552,7 +2607,7 @@ IndexError: list index out of range - +
TestRegister - test_registering_user
@@ -2561,7 +2616,7 @@ IndexError: list index out of range - +
TestRegister - test_registering_user_fail
@@ -2570,7 +2625,7 @@ IndexError: list index out of range - +
TestRegister - test_user_change_password
@@ -2588,13 +2643,13 @@ IndexError: list index out of range 0 1 - Detail + Detail - +
TestShelf - test_add_shelf_from_search
@@ -2603,7 +2658,7 @@ IndexError: list index out of range - +
TestShelf - test_adv_search_shelf
@@ -2612,7 +2667,7 @@ IndexError: list index out of range - +
TestShelf - test_arrange_shelf
@@ -2621,7 +2676,7 @@ IndexError: list index out of range - +
TestShelf - test_delete_book_of_shelf
@@ -2630,7 +2685,7 @@ IndexError: list index out of range - +
TestShelf - test_private_shelf
@@ -2639,7 +2694,7 @@ IndexError: list index out of range - +
TestShelf - test_public_private_shelf
@@ -2648,7 +2703,7 @@ IndexError: list index out of range - +
TestShelf - test_public_shelf
@@ -2657,7 +2712,7 @@ IndexError: list index out of range - +
TestShelf - test_rename_shelf
@@ -2666,7 +2721,7 @@ IndexError: list index out of range - +
TestShelf - test_shelf_action_non_shelf_edit_role
@@ -2675,7 +2730,7 @@ IndexError: list index out of range - +
TestShelf - test_shelf_anonymous
@@ -2684,19 +2739,19 @@ IndexError: list index out of range - +
TestShelf - test_shelf_database_change
- SKIP + SKIP
-