diff --git a/cps/admin.py b/cps/admin.py index 1d4b5a84..4038977e 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -37,7 +37,7 @@ from flask_babel import gettext as _ from sqlalchemy import and_ from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError -from sqlalchemy.sql.expression import func, or_ +from sqlalchemy.sql.expression import func, or_, text from . import constants, logger, helper, services from .cli import filepicker @@ -244,6 +244,13 @@ def list_users(): off = request.args.get("offset") or 0 limit = request.args.get("limit") or 10 search = request.args.get("search") + sort = request.args.get("sort") + order = request.args.get("order") + if sort and order: + order = text(sort + " " + order) + else: + order = ub.User.name.desc() + 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) @@ -252,10 +259,10 @@ def list_users(): users = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"), func.lower(ub.User.kindle_mail).ilike("%" + search + "%"), func.lower(ub.User.email).ilike("%" + search + "%")))\ - .offset(off).limit(limit).all() + .order_by(order).offset(off).limit(limit).all() filtered_count = len(users) else: - users = all_user.offset(off).limit(limit).all() + users = all_user.order_by(order).offset(off).limit(limit).all() filtered_count = total_count for user in users: @@ -335,6 +342,9 @@ def edit_list_user(param): elif param == 'kindle_mail': user.kindle_mail = valid_email(vals['value']) if vals['value'] else "" elif param == 'role': + if user.name == "Guest" and int(vals['field_index']) in \ + [constants.ROLE_ADMIN, constants.ROLE_PASSWD, constants.ROLE_EDIT_SHELFS]: + raise Exception(_("Guest can't have this role")) if vals['value'] == 'true': user.role |= int(vals['field_index']) else: @@ -345,6 +355,8 @@ def edit_list_user(param): 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 user.name == "Guest" and int(vals['field_index']) == constants.SIDEBAR_READ_AND_UNREAD: + raise Exception(_("Guest can't have this view")) if vals['value'] == 'true': user.sidebar_view |= int(vals['field_index']) else: @@ -358,6 +370,8 @@ def edit_list_user(param): elif param == 'denied_column_value': user.denied_column_value = vals['value'] elif param == 'locale': + if user.name == "Guest": + raise Exception(_("Guest's Locale is determined automatically and can't be set")) user.locale = vals['value'] elif param == 'default_language': user.default_language = vals['value'] @@ -1185,10 +1199,14 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): 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() - ub.session_commit() - flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success") - return redirect(url_for('admin.admin')) + if content.name != "Guest": + ub.session.query(ub.User).filter(ub.User.id == content.id).delete() + ub.session_commit() + flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success") + return redirect(url_for('admin.admin')) + else: + flash(_(u"Can't delete Guest User"), category="error") + return redirect(url_for('admin.admin')) else: flash(_(u"No admin user remaining, can't delete user", nick=content.name), category="error") return redirect(url_for('admin.admin')) @@ -1255,6 +1273,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): except OperationalError: ub.session.rollback() flash(_(u"Settings DB is not Writeable"), category="error") + return "" @admi.route("/admin/user/new", methods=["GET", "POST"]) @@ -1350,7 +1369,9 @@ def edit_user(user_id): kobo_support = feature_support['kobo'] and config.config_kobo_sync if request.method == "POST": to_save = request.form.to_dict() - _handle_edit_user(to_save, content, languages, translations, kobo_support) + resp = _handle_edit_user(to_save, content, languages, translations, kobo_support) + if resp: + return resp return render_title_template("user_edit.html", translations=translations, languages=languages, diff --git a/cps/config_sql.py b/cps/config_sql.py index 2ab0e3d6..3e5e4c59 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -24,7 +24,7 @@ import sys from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean, BLOB, JSON from sqlalchemy.exc import OperationalError try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base diff --git a/cps/db.py b/cps/db.py index 5cb04ed3..b875ded7 100644 --- a/cps/db.py +++ b/cps/db.py @@ -33,7 +33,7 @@ from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.exc import OperationalError try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base @@ -393,7 +393,7 @@ class AlchemyEncoder(json.JSONEncoder): if isinstance(o.__class__, DeclarativeMeta): # an SQLAlchemy class fields = {} - for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata']: + for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata' and x!="password"]: if field == 'books': continue data = o.__getattribute__(field) diff --git a/cps/editbooks.py b/cps/editbooks.py index 8cc960cc..580405b8 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -238,14 +238,14 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session): @editbook.route("/ajax/delete/") @login_required def delete_book_from_details(book_id): - return Response(delete_book(book_id,"", True), mimetype='application/json') + return Response(delete_book(book_id, "", True), mimetype='application/json') @editbook.route("/delete/", defaults={'book_format': ""}) @editbook.route("/delete//") @login_required def delete_book_ajax(book_id, book_format): - return delete_book(book_id,book_format, False) + return delete_book(book_id, book_format, False) def delete_whole_book(book_id, book): diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index a98d0b66..4c262661 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -29,7 +29,7 @@ from sqlalchemy import Column, UniqueConstraint from sqlalchemy import String, Integer from sqlalchemy.orm import sessionmaker, scoped_session try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 11ce6ed1..81308c64 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -142,10 +142,11 @@ $("#delete_confirm").click(function() { //get data-id attribute of the clicked element var deleteId = $(this).data("delete-id"); var bookFormat = $(this).data("delete-format"); + var ajaxResponse = $(this).data("ajax"); if (bookFormat) { window.location.href = getPath() + "/delete/" + deleteId + "/" + bookFormat; } else { - if ($(this).data("delete-format")) { + if (ajaxResponse) { path = getPath() + "/ajax/delete/" + deleteId; $.ajax({ method:"get", @@ -187,6 +188,7 @@ $("#deleteModal").on("show.bs.modal", function(e) { } $(e.currentTarget).find("#delete_confirm").data("delete-id", bookId); $(e.currentTarget).find("#delete_confirm").data("delete-format", bookfomat); + $(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax")); }); diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 96901d0b..a0503976 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -459,8 +459,8 @@ $(function() { $("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 - + $("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); + $(".user-remove[data-pk='"+guest.data("pk")+"']").prop("disabled", true); }, // eslint-disable-next-line no-unused-vars @@ -603,7 +603,7 @@ function EbookActions (value, row) { /* Function for deleting books */ function UserActions (value, row) { return [ - "
", + "
", "", "
" ].join(""); @@ -618,14 +618,14 @@ function responseHandler(res) { } function singleUserFormatter(value, row) { - return '' + this.buttontext + '' + return '' + this.buttontext + '' } function checkboxFormatter(value, row, index){ if(value & this.column) - return ''; + return ''; else - return ''; + return ''; } function checkboxChange(checkbox, userId, field, field_index) { @@ -732,6 +732,11 @@ function user_handle (userId) { }); } +function checkboxSorter(a, b, c, d) +{ + return a - b +} + function test(){ console.log("hello"); } diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index e7ddf156..b8a9d2c2 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -111,15 +111,16 @@ {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, true, 2) }} {{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), false, true, 3) }} {{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "download_role",_('Upload'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "upload_role", _('Download'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}} {{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "upload_role",_('Upload'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "download_role", _('Download'), visiblility, all_roles)}} {{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} {{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show read/unread selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show category selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show random books'), visiblility, sidebar_settings)}} diff --git a/cps/ub.py b/cps/ub.py index 6cbc0383..a85f7404 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -44,7 +44,7 @@ from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.sql.expression import func try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base diff --git a/cps/web.py b/cps/web.py index e1acdcef..658ff735 100644 --- a/cps/web.py +++ b/cps/web.py @@ -755,11 +755,12 @@ def books_table(): def list_books(): off = request.args.get("offset") or 0 limit = request.args.get("limit") or config.config_books_per_page - # sort = request.args.get("sort") - if request.args.get("order") == 'desc': - order = [db.Books.timestamp.desc()] + sort = request.args.get("sort") + order = request.args.get("order") + if sort and order: + order = [text(sort + " " + order)] else: - order = [db.Books.timestamp.asc()] + order = [db.Books.timestamp.desc()] search = request.args.get("search") total_count = calibre_db.session.query(db.Books).count() if search: