From 4583fc802300c470183618ab53d80c6118cf2a88 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 30 Mar 2025 12:03:12 +0200 Subject: [PATCH] Python 3.13 compatibility Code cosmetics --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- CONTRIBUTING.md | 8 +- SECURITY.md | 76 +- cps/db.py | 2 +- cps/editbooks.py | 2 +- cps/helper.py | 2 +- cps/kobo.py | 4 +- cps/kobo_auth.py | 4 +- cps/oauth.py | 2 +- cps/reverseproxy.py | 4 +- cps/services/gmail.py | 2 +- cps/services/goodreads_support.py | 2 +- cps/services/worker.py | 2 +- cps/static/css/libs/typeahead.css | 1 - cps/static/js/libs/plugins.js | 2 +- .../libs/pwstrength/pwstrength-bootstrap.js | 2 +- cps/static/js/main.js | 6 +- cps/tasks/mail.py | 4 +- cps/ub.py | 6 +- cps/web.py | 4 +- pyproject.toml | 1 + test/Calibre-Web TestSummary_Windows.html | 1027 ----------------- 23 files changed, 70 insertions(+), 1097 deletions(-) delete mode 100644 test/Calibre-Web TestSummary_Windows.html diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5a28406a..a4621025 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,7 +11,7 @@ assignees: '' After 6 years of more or less intensive programming on Calibre-Web, I need a break. The last few months, maintaining Calibre-Web has felt more like work than a hobby. I felt pressured and teased by people to solve "their" problems and merge PRs for "their" Calibre-Web. -I have turned off all notifications from Github/Discord and will now concentrate undisturbed on the development of “my” Calibre-Web over the next few weeks/months. +I have turned off all notifications from GitHub/Discord and will now concentrate undisturbed on the development of “my” Calibre-Web over the next few weeks/months. I will look into the issues and maybe also the PRs from time to time, but don't expect a quick response from me. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index dec77d51..3e5c7b9f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -11,7 +11,7 @@ assignees: '' After 6 years of more or less intensive programming on Calibre-Web, I need a break. The last few months, maintaining Calibre-Web has felt more like work than a hobby. I felt pressured and teased by people to solve "their" problems and merge PRs for "their" Calibre-Web. -I have turned off all notifications from Github/Discord and will now concentrate undisturbed on the development of “my” Calibre-Web over the next few weeks/months. +I have turned off all notifications from GitHub/Discord and will now concentrate undisturbed on the development of “my” Calibre-Web over the next few weeks/months. I will look into the issues and maybe also the PRs from time to time, but don't expect a quick response from me. Please have a look at our [Contributing Guidelines](https://github.com/janeczku/calibre-web/blob/master/CONTRIBUTING.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ab32cf9..5f538dfa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Some of the user languages in Calibre-Web having missing translations. We are ha ### **Documentation** -The Calibre-Web documentation is hosted in the Github [Wiki](https://github.com/janeczku/calibre-web/wiki). The Wiki is open to everybody, if you find a problem, feel free to correct it. If information is missing, you are welcome to add it. The content will be reviewed time by time. Please try to be consistent with the form with the other Wiki pages (e.g. the project name is Calibre-Web with 2 capital letters and a dash in between). +The Calibre-Web documentation is hosted in the GitHub [Wiki](https://github.com/janeczku/calibre-web/wiki). The Wiki is open to everybody, if you find a problem, feel free to correct it. If information is missing, you are welcome to add it. The content will be reviewed time by time. Please try to be consistent with the form with the other Wiki pages (e.g. the project name is Calibre-Web with 2 capital letters and a dash in between). ### **Reporting a bug** @@ -28,12 +28,12 @@ Do not open up a GitHub issue if the bug is a **security vulnerability** in Cali Ensure the **bug was not already reported** by searching on GitHub under [Issues](https://github.com/janeczku/calibre-web/issues). Please also check if a solution for your problem can be found in the [wiki](https://github.com/janeczku/calibre-web/wiki). -If you're unable to find an **open issue** addressing the problem, open a [new one](https://github.com/janeczku/calibre-web/issues/new/choose). Be sure to include a **title** and **clear description**, as much relevant information as possible, the **issue form** helps you providing the right information. Deleting the form and just pasting the stack trace doesn't speed up fixing the problem. If your issue could be resolved, consider closing the issue. +If you're unable to find an **open issue** addressing the problem, open a [new one](https://github.com/janeczku/calibre-web/issues/new/choose). Be sure to include a **title** and **clear description**, as much relevant information as possible, the **issue form** helps you provide the right information. Deleting the form and just pasting the stack trace doesn't speed up fixing the problem. If your issue could be resolved, consider closing the issue. ### **Feature Request** If there is a feature missing in Calibre-Web and you can't find a feature request in the [Issues](https://github.com/janeczku/calibre-web/issues) section, you could create a [feature request](https://github.com/janeczku/calibre-web/issues/new?assignees=&labels=&template=feature_request.md&title=). -We will not extend Calibre-Web with any more login abilities or add further files storages, or file syncing ability. Furthermore Calibre-Web is made for home usage for company in-house usage, so requests regarding any sorts of social interaction capability, payment routines, search engine or web site analytics integration will not be implemented. +We will not extend Calibre-Web with any more login abilities or add further files storages, or file syncing ability. Calibre-Web is made for home usage for company in-house usage, so requests regarding any sorts of social interaction capability, payment routines, search engine or website analytics integration will not be implemented. ### **Contributing code to Calibre-Web** @@ -42,5 +42,5 @@ Open a new GitHub pull request with the patch. Ensure the PR description clearly In case your code enhances features of Calibre-Web: Create your pull request for the development branch if your enhancement consists of more than some lines of code in a local section of Calibre-Webs code. This makes it easier to test it and check all implication before it's made public. Please check if your code runs with python 3, python 2 is no longer supported. If possible and the feature is related to operating system functions, try to check it on Windows and Linux. -Calibre-Web is automatically tested on Linux in combination with python 3.8. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests. +Calibre-Web is automatically tested on Linux in combination with python 3.8. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on GitHub. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests. A static code analysis is done by Codacy, but it's partly broken and doesn't run automatically. You could check your code with ESLint before contributing, a configuration file can be found in the projects root folder. diff --git a/SECURITY.md b/SECURITY.md index 62b42a63..c0861a18 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,44 +10,44 @@ To receive fixes for security vulnerabilities it is required to always upgrade t ## History -| Fixed in | Description |CVE number | -|---------------|--------------------------------------------------------------------------------------------------------------------------------|---------| -| 3rd July 2018 | Guest access acts as a backdoor || -| V 0.6.7 | Hardcoded secret key for sessions |CVE-2020-12627 | -| V 0.6.13 | Calibre-Web Metadata cross site scripting |CVE-2021-25964| -| V 0.6.13 | Name of Shelves are only visible to users who can access the corresponding shelf Thanks to @ibarrionuevo || -| V 0.6.13 | JavaScript could get executed in the description field. Thanks to @ranjit-git and Hagai Wechsler (WhiteSource) || -| V 0.6.13 | JavaScript could get executed in a custom column of type "comment" field || -| V 0.6.13 | JavaScript could get executed after converting a book to another format with a title containing javascript code || -| V 0.6.13 | JavaScript could get executed after converting a book to another format with a username containing javascript code || -| V 0.6.13 | JavaScript could get executed in the description series, categories or publishers title || -| V 0.6.13 | JavaScript could get executed in the shelf title || -| V 0.6.13 | Login with the old session cookie after logout. Thanks to @ibarrionuevo || -| V 0.6.14 | CSRF was possible. Thanks to @mik317 and Hagai Wechsler (WhiteSource) |CVE-2021-25965| -| V 0.6.14 | Migrated some routes to POST-requests (CSRF protection). Thanks to @scara31 |CVE-2021-4164| -| V 0.6.15 | Fix for "javascript:" script links in identifier. Thanks to @scara31 |CVE-2021-4170| -| V 0.6.15 | Cross-Site Scripting vulnerability on uploaded cover file names. Thanks to @ibarrionuevo || -| V 0.6.15 | Creating public shelfs is now denied if user is missing the edit public shelf right. Thanks to @ibarrionuevo || -| V 0.6.15 | Changed error message in case of trying to delete a shelf unauthorized. Thanks to @ibarrionuevo || -| V 0.6.16 | JavaScript could get executed on authors page. Thanks to @alicaz |CVE-2022-0352| -| V 0.6.16 | Localhost can no longer be used to upload covers. Thanks to @scara31 |CVE-2022-0339| -| V 0.6.16 | Another case where public shelfs could be created without permission is prevented. Thanks to @nhiephon |CVE-2022-0273| -| V 0.6.16 | It's prevented to get the name of a private shelfs. Thanks to @nhiephon |CVE-2022-0405| -| V 0.6.17 | The SSRF Protection can no longer be bypassed via an HTTP redirect. Thanks to @416e6e61 |CVE-2022-0767| -| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH |CVE-2022-0766| -| V 0.6.18 | Possible SQL Injection is prevented in user table Thanks to Iman Sharafaldin (Forward Security) |CVE-2022-30765| -| V 0.6.18 | The SSRF protection no longer can be bypassed by IPV6/IPV4 embedding. Thanks to @416e6e61 |CVE-2022-0939| -| V 0.6.18 | The SSRF protection no longer can be bypassed to connect to other servers in the local network. Thanks to @michaellrowley |CVE-2022-0990| -| V 0.6.20 | Credentials for emails are now stored encrypted || -| V 0.6.20 | Login is rate limited || -| V 0.6.20 | Passwordstrength can be forced || -| V 0.6.21 | SMTP server credentials are no longer returned to client || -| V 0.6.21 | Cross-site scripting (XSS) stored in href bypasses filter using data wrapper no longer possible || -| V 0.6.21 | Cross-site scripting (XSS) is no longer possible via pathchooser || -| V 0.6.21 | Error Handling at non existent rating, language, and user downloaded books was fixed || -| V 0.6.22 | Upload mimetype is checked to prevent malicious file content in the books library || -| V 0.6.22 | Cross-site scripting (XSS) stored in comments section is prevented better (switching from lxml to bleach for sanitizing strings) || -| V 0.6.23 | Cookies are no longer stored for opds basic authentication and proxy authentication || +| Fixed in | Description | CVE number | +|---------------|----------------------------------------------------------------------------------------------------------------------------------|----------------| +| 3rd July 2018 | Guest access acts as a backdoor | | +| V 0.6.7 | Hardcoded secret key for sessions | CVE-2020-12627 | +| V 0.6.13 | Calibre-Web Metadata cross site scripting | CVE-2021-25964 | +| V 0.6.13 | Name of Shelves are only visible to users who can access the corresponding shelf Thanks to @ibarrionuevo | | +| V 0.6.13 | JavaScript could get executed in the description field. Thanks to @ranjit-git and Hagai Wechsler (WhiteSource) | | +| V 0.6.13 | JavaScript could get executed in a custom column of type "comment" field | | +| V 0.6.13 | JavaScript could get executed after converting a book to another format with a title containing javascript code | | +| V 0.6.13 | JavaScript could get executed after converting a book to another format with a username containing javascript code | | +| V 0.6.13 | JavaScript could get executed in the description series, categories or publishers title | | +| V 0.6.13 | JavaScript could get executed in the shelf title | | +| V 0.6.13 | Login with the old session cookie after logout. Thanks to @ibarrionuevo | | +| V 0.6.14 | CSRF was possible. Thanks to @mik317 and Hagai Wechsler (WhiteSource) | CVE-2021-25965 | +| V 0.6.14 | Migrated some routes to POST-requests (CSRF protection). Thanks to @scara31 | CVE-2021-4164 | +| V 0.6.15 | Fix for "javascript:" script links in identifier. Thanks to @scara31 | CVE-2021-4170 | +| V 0.6.15 | Cross-Site Scripting vulnerability on uploaded cover file names. Thanks to @ibarrionuevo | | +| V 0.6.15 | Creating public shelfs is now denied if user is missing the edit public shelf right. Thanks to @ibarrionuevo | | +| V 0.6.15 | Changed error message in case of trying to delete a shelf unauthorized. Thanks to @ibarrionuevo | | +| V 0.6.16 | JavaScript could get executed on authors page. Thanks to @alicaz | CVE-2022-0352 | +| V 0.6.16 | Localhost can no longer be used to upload covers. Thanks to @scara31 | CVE-2022-0339 | +| V 0.6.16 | Another case where public shelfs could be created without permission is prevented. Thanks to @nhiephon | CVE-2022-0273 | +| V 0.6.16 | It's prevented to get the name of a private shelfs. Thanks to @nhiephon | CVE-2022-0405 | +| V 0.6.17 | The SSRF Protection can no longer be bypassed via an HTTP redirect. Thanks to @416e6e61 | CVE-2022-0767 | +| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH | CVE-2022-0766 | +| V 0.6.18 | Possible SQL Injection is prevented in user table Thanks to Iman Sharafaldin (Forward Security) | CVE-2022-30765 | +| V 0.6.18 | The SSRF protection no longer can be bypassed by IPV6/IPV4 embedding. Thanks to @416e6e61 | CVE-2022-0939 | +| V 0.6.18 | The SSRF protection no longer can be bypassed to connect to other servers in the local network. Thanks to @michaellrowley | CVE-2022-0990 | +| V 0.6.20 | Credentials for emails are now stored encrypted | | +| V 0.6.20 | Login is rate limited | | +| V 0.6.20 | Passwordstrength can be forced | | +| V 0.6.21 | SMTP server credentials are no longer returned to client | | +| V 0.6.21 | Cross-site scripting (XSS) stored in href bypasses filter using data wrapper no longer possible | | +| V 0.6.21 | Cross-site scripting (XSS) is no longer possible via pathchooser | | +| V 0.6.21 | Error Handling at non existent rating, language, and user downloaded books was fixed | | +| V 0.6.22 | Upload mimetype is checked to prevent malicious file content in the books library | | +| V 0.6.22 | Cross-site scripting (XSS) stored in comments section is prevented better (switching from lxml to bleach for sanitizing strings) | | +| V 0.6.23 | Cookies are no longer stored for opds basic authentication and proxy authentication | | diff --git a/cps/db.py b/cps/db.py index c54d3de3..15921d7a 100644 --- a/cps/db.py +++ b/cps/db.py @@ -193,7 +193,7 @@ class Identifiers(Base): elif format_type == "issn": return "https://portal.issn.org/resource/ISSN/{0}".format(self.val) elif format_type == "isfdb": - return "http://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val) + return "https://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val) elif format_type == "databazeknih": return "https://www.databazeknih.cz/knihy/{0}".format(self.val) elif format_type == "storygraph": diff --git a/cps/editbooks.py b/cps/editbooks.py index 355a9bfd..948ad1ab 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -76,7 +76,7 @@ def edit_required(f): @editbook.route("/ajax/delete/", methods=["POST"]) @user_login_required def delete_book_from_details(book_id): - return delete_book_from_table(book_id, "", True) # , mimetype='application/json') + return delete_book_from_table(book_id, "", True) @editbook.route("/delete/", defaults={'book_format': ""}, methods=["POST"]) diff --git a/cps/helper.py b/cps/helper.py index 4d4fdeb4..8fa9bbac 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -513,7 +513,7 @@ def update_dir_structure_gdrive(book_id, first_author): book.path = new_authordir + '/' + book.path.split('/')[1] gd.updateDatabaseOnEdit(g_file['id'], book.path) else: - return _('File %(file)s not found on Google Drive', file=authordir) # file not found''' + return _('File %(file)s not found on Google Drive', file=authordir) # file not found if titledir != new_titledir or authordir != new_authordir : all_new_name = get_valid_filename(book.title, chars=42) + ' - ' \ + get_valid_filename(new_authordir, chars=42) diff --git a/cps/kobo.py b/cps/kobo.py index da6320df..cfdc60c9 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -328,7 +328,7 @@ def generate_sync_response(sync_token, sync_results, set_cont=False): sync_token.to_headers(extra_headers) # log.debug("Kobo Sync Content: {}".format(sync_results)) - # jsonify decodes the unicode string different to what kobo expects + # jsonify decodes the Unicode string different to what kobo expects response = make_response(json.dumps(sync_results), extra_headers) response.headers["Content-Type"] = "application/json; charset=utf-8" return response @@ -732,7 +732,7 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False): ub.session_commit() -# Creates a Kobo "Tag" object from a ub.Shelf object +# Creates a Kobo "Tag" object from an ub.Shelf object def create_kobo_tag(shelf): tag = { "Created": convert_to_kobo_timestamp_string(shelf.created), diff --git a/cps/kobo_auth.py b/cps/kobo_auth.py index f99bf77c..53f35dcc 100644 --- a/cps/kobo_auth.py +++ b/cps/kobo_auth.py @@ -22,7 +22,7 @@ This module also includes research notes into the auth protocol used by Kobo devices. Log-in: -When first booting a Kobo device the user must sign into a Kobo (or affiliate) account. +When first booting a Kobo device the user must log in to a Kobo (or affiliate) account. Upon successful sign-in, the user is redirected to https://auth.kobobooks.com/CrossDomainSignIn?id= which serves the following response: @@ -41,7 +41,7 @@ issue for a few years now https://www.mobileread.com/forums/showpost.php?p=34768 will still grant access given the userkey.) Official Kobo Store Api authorization: -* For most of the endpoints we care about (sync, metadata, tags, etc), the userKey is +* For most of the endpoints we care about (sync, metadata, tags, etc.), the userKey is passed in the x-kobo-userkey header, and is sufficient to authorize the API call. * Some endpoints (e.g: AnnotationService) instead make use of Bearer tokens pass through an authorization header. To get a BearerToken, the device makes a POST request to the diff --git a/cps/oauth.py b/cps/oauth.py index 0caa61ec..22e92733 100644 --- a/cps/oauth.py +++ b/cps/oauth.py @@ -32,7 +32,7 @@ class OAuthBackend(SQLAlchemyBackend): Stores and retrieves OAuth tokens using a relational database through the `SQLAlchemy`_ ORM. - .. _SQLAlchemy: https://www.sqlalchemy.org/ + _SQLAlchemy: https://www.sqlalchemy.org/ """ def __init__(self, model, session, provider_id, user=None, user_id=None, user_required=None, anon_user=None, diff --git a/cps/reverseproxy.py b/cps/reverseproxy.py index 4acb8e45..887590bf 100644 --- a/cps/reverseproxy.py +++ b/cps/reverseproxy.py @@ -41,9 +41,9 @@ class ReverseProxied(object): """Wrap the application in this middleware and configure the front-end server to add these headers, to let you quietly bind this to a URL other than / and to an HTTP scheme that is - different than what is used locally. + different from what is used locally. - Code courtesy of: http://flask.pocoo.org/snippets/35/ + Code courtesy of: https://flask.pocoo.org/snippets/35/ In nginx: location /myprefix { diff --git a/cps/services/gmail.py b/cps/services/gmail.py index 5b0cdbe5..67b346a8 100644 --- a/cps/services/gmail.py +++ b/cps/services/gmail.py @@ -92,7 +92,7 @@ def send_messsage(token, msg): if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) service = build('gmail', 'v1', credentials=creds) - message_as_bytes = msg.as_bytes() # the message should converted from string to bytes. + message_as_bytes = msg.as_bytes() # the message should be converted from string to bytes. message_as_base64 = base64.urlsafe_b64encode(message_as_bytes) # encode in base64 (printable letters coding) raw = message_as_base64.decode() # convert to something JSON serializable body = {'raw': raw} diff --git a/cps/services/goodreads_support.py b/cps/services/goodreads_support.py index b3b7bdef..c3bdbacb 100644 --- a/cps/services/goodreads_support.py +++ b/cps/services/goodreads_support.py @@ -117,7 +117,7 @@ def get_author_info(author_name): def get_other_books(author_info, library_books=None): - # Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates + # Get all identifiers (ISBN, Goodreads, etc.) and filter author's books by that list so we show fewer duplicates # Note: Not all images will be shown, even though they're available on Goodreads.com. # See https://www.goodreads.com/topic/show/18213769-goodreads-book-images diff --git a/cps/services/worker.py b/cps/services/worker.py index 2a0809ca..a804f633 100644 --- a/cps/services/worker.py +++ b/cps/services/worker.py @@ -235,7 +235,7 @@ class CalibreTask: @property def dead(self): - """Determines whether or not this task can be garbage collected + """Determines whether this task can be garbage collected We have a separate dictating this because there may be certain tasks that want to override this """ diff --git a/cps/static/css/libs/typeahead.css b/cps/static/css/libs/typeahead.css index fcc17a5b..b00303fe 100644 --- a/cps/static/css/libs/typeahead.css +++ b/cps/static/css/libs/typeahead.css @@ -157,7 +157,6 @@ fieldset[disabled] .twitter-typeahead .tt-input { list-style: none; font-size: 14px; background-color: #ffffff; - border: 1px solid #cccccc; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 4px; -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); diff --git a/cps/static/js/libs/plugins.js b/cps/static/js/libs/plugins.js index ee7ded31..9190ea2f 100644 --- a/cps/static/js/libs/plugins.js +++ b/cps/static/js/libs/plugins.js @@ -39,7 +39,7 @@ !function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,(function(t,e){let i=t.console,n=void 0===i?function(){}:function(t){i.error(t)};return function(i,o,s){(s=s||e||t.jQuery)&&(o.prototype.option||(o.prototype.option=function(t){t&&(this.options=Object.assign(this.options||{},t))}),s.fn[i]=function(t,...e){return"string"==typeof t?function(t,e,o){let r,l=`$().${i}("${e}")`;return t.each((function(t,h){let a=s.data(h,i);if(!a)return void n(`${i} not initialized. Cannot call method ${l}`);let c=a[e];if(!c||"_"==e.charAt(0))return void n(`${l} is not a valid method`);let u=c.apply(a,o);r=void 0===r?u:r})),void 0!==r?r:t}(this,t,e):(r=t,this.each((function(t,e){let n=s.data(e,i);n?(n.option(r),n._init()):(n=new o(e,r),s.data(e,i,n))})),this);var r})}})),function(t,e){"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,(function(){function t(){}let e=t.prototype;return e.on=function(t,e){if(!t||!e)return this;let i=this._events=this._events||{},n=i[t]=i[t]||[];return n.includes(e)||n.push(e),this},e.once=function(t,e){if(!t||!e)return this;this.on(t,e);let i=this._onceEvents=this._onceEvents||{};return(i[t]=i[t]||{})[e]=!0,this},e.off=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;let n=i.indexOf(e);return-1!=n&&i.splice(n,1),this},e.emitEvent=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;i=i.slice(0),e=e||[];let n=this._onceEvents&&this._onceEvents[t];for(let o of i){n&&n[o]&&(this.off(t,o),delete n[o]),o.apply(this,e)}return this},e.allOff=function(){return delete this._events,delete this._onceEvents,this},t})),function(t,e){"object"==typeof module&&module.exports?module.exports=e(t):t.fizzyUIUtils=e(t)}(this,(function(t){let e={extend:function(t,e){return Object.assign(t,e)},modulo:function(t,e){return(t%e+e)%e},makeArray:function(t){if(Array.isArray(t))return t;if(null==t)return[];return"object"==typeof t&&"number"==typeof t.length?[...t]:[t]},removeFrom:function(t,e){let i=t.indexOf(e);-1!=i&&t.splice(i,1)},getParent:function(t,e){for(;t.parentNode&&t!=document.body;)if((t=t.parentNode).matches(e))return t},getQueryElement:function(t){return"string"==typeof t?document.querySelector(t):t},handleEvent:function(t){let e="on"+t.type;this[e]&&this[e](t)},filterFindElements:function(t,i){return(t=e.makeArray(t)).filter((t=>t instanceof HTMLElement)).reduce(((t,e)=>{if(!i)return t.push(e),t;e.matches(i)&&t.push(e);let n=e.querySelectorAll(i);return t=t.concat(...n)}),[])},debounceMethod:function(t,e,i){i=i||100;let n=t.prototype[e],o=e+"Timeout";t.prototype[e]=function(){clearTimeout(this[o]);let t=arguments;this[o]=setTimeout((()=>{n.apply(this,t),delete this[o]}),i)}},docReady:function(t){let e=document.readyState;"complete"==e||"interactive"==e?setTimeout(t):document.addEventListener("DOMContentLoaded",t)},toDashed:function(t){return t.replace(/(.)([A-Z])/g,(function(t,e,i){return e+"-"+i})).toLowerCase()}},i=t.console;return e.htmlInit=function(n,o){e.docReady((function(){let s="data-"+e.toDashed(o),r=document.querySelectorAll(`[${s}]`),l=t.jQuery;[...r].forEach((t=>{let e,r=t.getAttribute(s);try{e=r&&JSON.parse(r)}catch(e){return void(i&&i.error(`Error parsing ${s} on ${t.className}: ${e}`))}let h=new n(t,e);l&&l.data(t,o,h)}))}))},e})),function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter"),require("fizzy-ui-utils")):t.InfiniteScroll=e(t,t.EvEmitter,t.fizzyUIUtils)}(window,(function(t,e,i){let n=t.jQuery,o={};function s(t,e){let r=i.getQueryElement(t);if(r){if((t=r).infiniteScrollGUID){let i=o[t.infiniteScrollGUID];return i.option(e),i}this.element=t,this.options={...s.defaults},this.option(e),n&&(this.$element=n(this.element)),this.create()}else console.error("Bad element for InfiniteScroll: "+(r||t))}s.defaults={},s.create={},s.destroy={};let r=s.prototype;Object.assign(r,e.prototype);let l=0;r.create=function(){let t=this.guid=++l;if(this.element.infiniteScrollGUID=t,o[t]=this,this.pageIndex=1,this.loadCount=0,this.updateGetPath(),this.getPath&&this.getPath()){this.updateGetAbsolutePath(),this.log("initialized",[this.element.className]),this.callOnInit();for(let t in s.create)s.create[t].call(this)}else console.error("Disabling InfiniteScroll")},r.option=function(t){Object.assign(this.options,t)},r.callOnInit=function(){let t=this.options.onInit;t&&t.call(this,this)},r.dispatchEvent=function(t,e,i){this.log(t,i);let o=e?[e].concat(i):i;if(this.emitEvent(t,o),!n||!this.$element)return;let s=t+=".infiniteScroll";if(e){let i=n.Event(e);i.type=t,s=i}this.$element.trigger(s,i)};let h={initialized:t=>`on ${t}`,request:t=>`URL: ${t}`,load:(t,e)=>`${t.title||""}. URL: ${e}`,error:(t,e)=>`${t}. URL: ${e}`,append:(t,e,i)=>`${i.length} items. URL: ${e}`,last:(t,e)=>`URL: ${e}`,history:(t,e)=>`URL: ${e}`,pageIndex:function(t,e){return`current page determined to be: ${t} from ${e}`}};r.log=function(t,e){if(!this.options.debug)return;let i=`[InfiniteScroll] ${t}`,n=h[t];n&&(i+=". "+n.apply(this,e)),console.log(i)},r.updateMeasurements=function(){this.windowHeight=t.innerHeight;let e=this.element.getBoundingClientRect();this.top=e.top+t.scrollY},r.updateScroller=function(){let e=this.options.elementScroll;if(e){if(this.scroller=!0===e?this.element:i.getQueryElement(e),!this.scroller)throw new Error(`Unable to find elementScroll: ${e}`)}else this.scroller=t},r.updateGetPath=function(){let t=this.options.path;if(!t)return void console.error(`InfiniteScroll path option required. Set as: ${t}`);let e=typeof t;"function"!=e?"string"==e&&t.match("{{#}}")?this.updateGetPathTemplate(t):this.updateGetPathSelector(t):this.getPath=t},r.updateGetPathTemplate=function(t){this.getPath=()=>{let e=this.pageIndex+1;return t.replace("{{#}}",e)};let e=t.replace(/(\\\?|\?)/,"\\?").replace("{{#}}","(\\d\\d?\\d?)"),i=new RegExp(e),n=location.href.match(i);n&&(this.pageIndex=parseInt(n[1],10),this.log("pageIndex",[this.pageIndex,"template string"]))};let a=[/^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/,/^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/,/(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/],c=s.getPathParts=function(t){if(t)for(let e of a){let i=t.match(e);if(i){let[,t,e,n]=i;return{begin:t,index:e,end:n}}}};r.updateGetPathSelector=function(t){let e=document.querySelector(t);if(!e)return void console.error(`Bad InfiniteScroll path option. Next link not found: ${t}`);let i=e.getAttribute("href"),n=c(i);if(!n)return void console.error(`InfiniteScroll unable to parse next link href: ${i}`);let{begin:o,index:s,end:r}=n;this.isPathSelector=!0,this.getPath=()=>o+(this.pageIndex+1)+r,this.pageIndex=parseInt(s,10)-1,this.log("pageIndex",[this.pageIndex,"next link"])},r.updateGetAbsolutePath=function(){let t=this.getPath();if(t.match(/^http/)||t.match(/^\//))return void(this.getAbsolutePath=this.getPath);let{pathname:e}=location,i=t.match(/^\?/),n=e.substring(0,e.lastIndexOf("/")),o=i?e:n+"/";this.getAbsolutePath=()=>o+this.getPath()},s.create.hideNav=function(){let t=i.getQueryElement(this.options.hideNav);t&&(t.style.display="none",this.nav=t)},s.destroy.hideNav=function(){this.nav&&(this.nav.style.display="")},r.destroy=function(){this.allOff();for(let t in s.destroy)s.destroy[t].call(this);delete this.element.infiniteScrollGUID,delete o[this.guid],n&&this.$element&&n.removeData(this.element,"infiniteScroll")},s.throttle=function(t,e){let i,n;return e=e||200,function(){let o=+new Date,s=arguments,r=()=>{i=o,t.apply(this,s)};i&&o{if(!i.ok){let t=new Error(i.statusText);return this.onPageError(t,o,i),{response:i}}return i[t]().then((s=>("text"==t&&e&&(s=n.parseFromString(s,"text/html")),204==i.status?(this.lastPageReached(s,o),{body:s,response:i}):this.onPageLoad(s,o,i))))})).catch((t=>{this.onPageError(t,o)}));return this.dispatchEvent("request",null,[o,s]),s},i.onPageLoad=function(t,e,i){return this.options.append||(this.isLoading=!1),this.pageIndex++,this.loadCount++,this.dispatchEvent("load",null,[t,e,i]),this.appendNextPage(t,e,i)},i.appendNextPage=function(t,e,i){let{append:n,responseBody:s,domParseResponse:r}=this.options;if(!("text"==s&&r)||!n)return{body:t,response:i};let l=t.querySelectorAll(n),h={body:t,response:i,items:l};if(!l||!l.length)return this.lastPageReached(t,e),h;let a=o(l),c=()=>(this.appendItems(l,a),this.isLoading=!1,this.dispatchEvent("append",null,[t,e,l,i]),h);return this.options.outlayer?this.appendOutlayerItems(a,c):c()},i.appendItems=function(t,e){t&&t.length&&(function(t){let e=t.querySelectorAll("script");for(let t of e){let e=document.createElement("script"),i=t.attributes;for(let t of i)e.setAttribute(t.name,t.value);e.innerHTML=t.innerHTML,t.parentNode.replaceChild(e,t)}}(e=e||o(t)),this.element.appendChild(e))},i.appendOutlayerItems=function(i,n){let o=e.imagesLoaded||t.imagesLoaded;return o?new Promise((function(t){o(i,(function(){let e=n();t(e)}))})):(console.error("[InfiniteScroll] imagesLoaded required for outlayer option"),void(this.isLoading=!1))},i.onAppendOutlayer=function(t,e,i){this.options.outlayer.appended(i)},i.checkLastPage=function(t,e){let i,{checkLastPage:n,path:o}=this.options;if(n){if("function"==typeof o){if(!this.getPath())return void this.lastPageReached(t,e)}"string"==typeof n?i=n:this.isPathSelector&&(i=o),i&&t.querySelector&&(t.querySelector(i)||this.lastPageReached(t,e))}},i.lastPageReached=function(t,e){this.canLoad=!1,this.dispatchEvent("last",null,[t,e])},i.onPageError=function(t,e,i){return this.isLoading=!1,this.canLoad=!1,this.dispatchEvent("error",null,[t,e,i]),t},e.create.prefill=function(){if(!this.options.prefill)return;let t=this.options.append;t?(this.updateMeasurements(),this.updateScroller(),this.isPrefilling=!0,this.on("append",this.prefill),this.once("error",this.stopPrefill),this.once("last",this.stopPrefill),this.prefill()):console.error(`append option required for prefill. Set as :${t}`)},i.prefill=function(){let t=this.getPrefillDistance();this.isPrefilling=t>=0,this.isPrefilling?(this.log("prefill"),this.loadNextPage()):this.stopPrefill()},i.getPrefillDistance=function(){return this.options.elementScroll?this.scroller.clientHeight-this.scroller.scrollHeight:this.windowHeight-this.element.clientHeight},i.stopPrefill=function(){this.log("stopPrefill"),this.off("append",this.prefill)},e})),function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("./core"),require("fizzy-ui-utils")):e(t,t.InfiniteScroll,t.fizzyUIUtils)}(window,(function(t,e,i){let n=e.prototype;return Object.assign(e.defaults,{scrollThreshold:400}),e.create.scrollWatch=function(){this.pageScrollHandler=this.onPageScroll.bind(this),this.resizeHandler=this.onResize.bind(this);let t=this.options.scrollThreshold;(t||0===t)&&this.enableScrollWatch()},e.destroy.scrollWatch=function(){this.disableScrollWatch()},n.enableScrollWatch=function(){this.isScrollWatching||(this.isScrollWatching=!0,this.updateMeasurements(),this.updateScroller(),this.on("last",this.disableScrollWatch),this.bindScrollWatchEvents(!0))},n.disableScrollWatch=function(){this.isScrollWatching&&(this.bindScrollWatchEvents(!1),delete this.isScrollWatching)},n.bindScrollWatchEvents=function(e){let i=e?"addEventListener":"removeEventListener";this.scroller[i]("scroll",this.pageScrollHandler),t[i]("resize",this.resizeHandler)},n.onPageScroll=e.throttle((function(){this.getBottomDistance()<=this.options.scrollThreshold&&this.dispatchEvent("scrollThreshold")})),n.getBottomDistance=function(){let e,i;return this.options.elementScroll?(e=this.scroller.scrollHeight,i=this.scroller.scrollTop+this.scroller.clientHeight):(e=this.top+this.element.clientHeight,i=t.scrollY+this.windowHeight),e-i},n.onResize=function(){this.updateMeasurements()},i.debounceMethod(e,"onResize",150),e})),function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("./core"),require("fizzy-ui-utils")):e(t,t.InfiniteScroll,t.fizzyUIUtils)}(window,(function(t,e,i){let n=e.prototype;Object.assign(e.defaults,{history:"replace"});let o=document.createElement("a");return e.create.history=function(){if(!this.options.history)return;o.href=this.getAbsolutePath(),(o.origin||o.protocol+"//"+o.host)==location.origin?this.options.append?this.createHistoryAppend():this.createHistoryPageLoad():console.error(`[InfiniteScroll] cannot set history with different origin: ${o.origin} on ${location.origin} . History behavior disabled.`)},n.createHistoryAppend=function(){this.updateMeasurements(),this.updateScroller(),this.scrollPages=[{top:0,path:location.href,title:document.title}],this.scrollPage=this.scrollPages[0],this.scrollHistoryHandler=this.onScrollHistory.bind(this),this.unloadHandler=this.onUnload.bind(this),this.scroller.addEventListener("scroll",this.scrollHistoryHandler),this.on("append",this.onAppendHistory),this.bindHistoryAppendEvents(!0)},n.bindHistoryAppendEvents=function(e){let i=e?"addEventListener":"removeEventListener";this.scroller[i]("scroll",this.scrollHistoryHandler),t[i]("unload",this.unloadHandler)},n.createHistoryPageLoad=function(){this.on("load",this.onPageLoadHistory)},e.destroy.history=n.destroyHistory=function(){this.options.history&&this.options.append&&this.bindHistoryAppendEvents(!1)},n.onAppendHistory=function(t,e,i){if(!i||!i.length)return;let n=i[0],s=this.getElementScrollY(n);o.href=e,this.scrollPages.push({top:s,path:o.href,title:t.title})},n.getElementScrollY=function(e){if(this.options.elementScroll)return e.offsetTop-this.top;return e.getBoundingClientRect().top+t.scrollY},n.onScrollHistory=function(){let t=this.getClosestScrollPage();t!=this.scrollPage&&(this.scrollPage=t,this.setHistory(t.title,t.path))},i.debounceMethod(e,"onScrollHistory",150),n.getClosestScrollPage=function(){let e,i;e=this.options.elementScroll?this.scroller.scrollTop+this.scroller.clientHeight/2:t.scrollY+this.windowHeight/2;for(let t of this.scrollPages){if(t.top>=e)break;i=t}return i},n.setHistory=function(t,e){let i=this.options.history;i&&history[i+"State"]&&(history[i+"State"](null,t,e),this.options.historyTitle&&(document.title=t),this.dispatchEvent("history",null,[t,e]))},n.onUnload=function(){if(0===this.scrollPage.top)return;let e=t.scrollY-this.scrollPage.top+this.top;this.destroyHistory(),scrollTo(0,e)},n.onPageLoadHistory=function(t,e){this.setHistory(t.title,e)},e})),function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("./core"),require("fizzy-ui-utils")):e(t,t.InfiniteScroll,t.fizzyUIUtils)}(window,(function(t,e,i){class n{constructor(t,e){this.element=t,this.infScroll=e,this.clickHandler=this.onClick.bind(this),this.element.addEventListener("click",this.clickHandler),e.on("request",this.disable.bind(this)),e.on("load",this.enable.bind(this)),e.on("error",this.hide.bind(this)),e.on("last",this.hide.bind(this))}onClick(t){t.preventDefault(),this.infScroll.loadNextPage()}enable(){this.element.removeAttribute("disabled")}disable(){this.element.disabled="disabled"}hide(){this.element.style.display="none"}destroy(){this.element.removeEventListener("click",this.clickHandler)}}return e.create.button=function(){let t=i.getQueryElement(this.options.button);t&&(this.button=new n(t,this))},e.destroy.button=function(){this.button&&this.button.destroy()},e.Button=n,e})),function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("./core"),require("fizzy-ui-utils")):e(t,t.InfiniteScroll,t.fizzyUIUtils)}(window,(function(t,e,i){let n=e.prototype;function o(t){r(t,"none")}function s(t){r(t,"block")}function r(t,e){t&&(t.style.display=e)}return e.create.status=function(){let t=i.getQueryElement(this.options.status);t&&(this.statusElement=t,this.statusEventElements={request:t.querySelector(".infinite-scroll-request"),error:t.querySelector(".infinite-scroll-error"),last:t.querySelector(".infinite-scroll-last")},this.on("request",this.showRequestStatus),this.on("error",this.showErrorStatus),this.on("last",this.showLastStatus),this.bindHideStatus("on"))},n.bindHideStatus=function(t){let e=this.options.append?"append":"load";this[t](e,this.hideAllStatus)},n.showRequestStatus=function(){this.showStatus("request")},n.showErrorStatus=function(){this.showStatus("error")},n.showLastStatus=function(){this.showStatus("last"),this.bindHideStatus("off")},n.showStatus=function(t){s(this.statusElement),this.hideStatusEventElements(),s(this.statusEventElements[t])},n.hideAllStatus=function(){o(this.statusElement),this.hideStatusEventElements()},n.hideStatusEventElements=function(){for(let t in this.statusEventElements){o(this.statusEventElements[t])}},e})), /*! * imagesLoaded v4.1.4 - * JavaScript is all like "You images are done yet or what?" + * JavaScript is all like "Your images are done yet or what?" * MIT License */ function(t,e){"use strict";"function"==typeof define&&define.amd?define(["ev-emitter/ev-emitter"],(function(i){return e(t,i)})):"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}("undefined"!=typeof window?window:this,(function(t,e){"use strict";var i=t.jQuery,n=t.console;function o(t,e){for(var i in e)t[i]=e[i];return t}var s=Array.prototype.slice;function r(t,e,l){if(!(this instanceof r))return new r(t,e,l);var h,a=t;("string"==typeof t&&(a=document.querySelectorAll(t)),a)?(this.elements=(h=a,Array.isArray(h)?h:"object"==typeof h&&"number"==typeof h.length?s.call(h):[h]),this.options=o({},this.options),"function"==typeof e?l=e:o(this.options,e),l&&this.on("always",l),this.getImages(),i&&(this.jqDeferred=new i.Deferred),setTimeout(this.check.bind(this))):n.error("Bad element for imagesLoaded "+(a||t))}r.prototype=Object.create(e.prototype),r.prototype.options={},r.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)},r.prototype.addElementImages=function(t){"IMG"==t.nodeName&&this.addImage(t),!0===this.options.background&&this.addElementBackgroundImages(t);var e=t.nodeType;if(e&&l[e]){for(var i=t.querySelectorAll("img"),n=0;n //
...
@@ -63,7 +63,7 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () { }); }); -// Generic control/related handler to show/hide fields based on a select' value +// Generic control/related handler to show/hide fields based on a 'select' value $(document).on("change", "select[data-control]", function() { var $this = $(this); var name = $this.data("control"); @@ -79,7 +79,7 @@ $(document).on("change", "select[data-control]", function() { } }); -// Generic control/related handler to show/hide fields based on a select' value +// Generic control/related handler to show/hide fields based on a 'select' value // this one is made to show all values if select value is not 0 $(document).on("change", "select[data-controlall]", function() { var $this = $(this); diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 7206b732..707a29a3 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -55,7 +55,7 @@ class EmailBase: return (code, resp) def send(self, strg): - """Send `strg' to the server.""" + """Send 'strg' to the server.""" log.debug_no_auth('send: {}'.format(strg[:300]), stacklevel=2) if hasattr(self, 'sock') and self.sock: try: @@ -102,7 +102,7 @@ class Email(EmailBase, smtplib.SMTP): smtplib.SMTP.__init__(self, *args, **kwargs) -# Class for sending ssl encrypted email with ability to get current progress, , derived from emailbase class +# Class for sending ssl encrypted email with ability to get current progress, derived from emailbase class class EmailSSL(EmailBase, smtplib.SMTP_SSL): def __init__(self, *args, **kwargs): diff --git a/cps/ub.py b/cps/ub.py index 0e15b8e2..20f0ac91 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -233,7 +233,7 @@ class UserBase: return '' % self.name -# Baseclass for Users in Calibre-Web, settings which are depending on certain users are stored here. It is derived from +# Baseclass for Users in Calibre-Web, settings which depend on certain users are stored here. It is derived from # User Base (all access methods are declared there) class User(UserBase, Base): __tablename__ = 'user' @@ -601,7 +601,7 @@ def migrate_user_session_table(engine, _session): trans.commit() -# Migrate database to current version, has to be updated after every database change. Currently migration from +# Migrate database to current version, has to be updated after every database change. Currently, migration from # maybe 4/5 versions back to current should work. # Migration is done by checking if relevant columns are existing, and then adding rows with SQL commands def migrate_Database(_session): @@ -636,7 +636,7 @@ def update_download(book_id, user_id): session.rollback() -# Delete non existing downloaded books in calibre-web's own database +# Delete non-existing downloaded books in calibre-web's own database def delete_download(book_id): session.query(Downloads).filter(book_id == Downloads.book_id).delete() try: diff --git a/cps/web.py b/cps/web.py index b3688257..31e124e9 100644 --- a/cps/web.py +++ b/cps/web.py @@ -60,7 +60,7 @@ from .services.worker import WorkerThread from .tasks_status import render_task_status from .usermanagement import user_login_required from .string_helper import strip_whitespaces -import traceback + feature_support = { 'ldap': bool(services.ldap), @@ -112,7 +112,7 @@ def add_security_headers(resp): resp.headers['X-Content-Type-Options'] = 'nosniff' resp.headers['X-Frame-Options'] = 'SAMEORIGIN' resp.headers['X-XSS-Protection'] = '1; mode=block' - resp.headers['Strict-Transport-Security'] = 'max-age=31536000'; + resp.headers['Strict-Transport-Security'] = 'max-age=31536000' return resp diff --git a/pyproject.toml b/pyproject.toml index 3c3bffbf..778e65e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", ] keywords = [ diff --git a/test/Calibre-Web TestSummary_Windows.html b/test/Calibre-Web TestSummary_Windows.html deleted file mode 100644 index 21a41612..00000000 --- a/test/Calibre-Web TestSummary_Windows.html +++ /dev/null @@ -1,1027 +0,0 @@ - - - - Calibre-Web Tests - - - - - - - - - - - - - - - - - - - -
-

Calibre-Web Tests

-
-
-
-
-
-
- -
-
-
-
-
-
- -

Start Time: 2021-10-06 00:29:44

- -
-
-
-
- -

Stop Time: 2021-10-06 00:37:52

- -
-
-
-
-

Duration: 7:28 min

-
-
-
-
-
- -
-
-
-
-
- - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Test Group/Test caseCountPassFailErrorSkipView
TestShelf2419131 - Detail -
-
TestShelf - test_add_shelf_from_search
-
PASS
-
TestShelf - test_adv_search_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_adv_search_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_arrange_shelf
-
-
- FAIL -
- - - -
-
TestShelf - test_arrange_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_delete_book_of_shelf
-
-
- FAIL -
- - - -
-
TestShelf - test_delete_book_of_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_private_shelf
-
-
- FAIL -
- - - -
-
TestShelf - test_private_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_public_private_shelf
-
-
- FAIL -
- - - -
-
TestShelf - test_public_private_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_public_shelf
-
-
- FAIL -
- - - -
-
TestShelf - test_public_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_rename_shelf
-
-
- FAIL -
- - - -
-
TestShelf - test_rename_shelf
-
-
- ERROR -
- - - -
-
TestShelf - test_shelf_action_non_shelf_edit_role
-
-
- FAIL -
- - - -
-
TestShelf - test_shelf_action_non_shelf_edit_role
-
-
- ERROR -
- - - -
-
TestShelf - test_shelf_anonymous
-
-
- ERROR -
- - - -
-
TestShelf - test_shelf_anonymous
-
-
- ERROR -
- - - -
-
TestShelf - test_shelf_database_change
-
-
- SKIP -
- - - -
-
TestShelf - test_shelf_long_name
-
-
- FAIL -
- - - -
-
TestShelf - test_shelf_long_name
-
-
- ERROR -
- - - -
-
TestShelf - test_xss_shelf
-
-
- FAIL -
- - - -
-
TestShelf - test_xss_shelf
-
-
- ERROR -
- - - -
Total2419131 
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Program libraryInstalled VersionTest class
PlatformWindows 10 10.0.19041 AMD64 Family 23 Model 113 Stepping 0, AuthenticAMD AMD64Basic
Python3.8.5Basic
APScheduler3.7.0Basic
Babel2.9.1Basic
backports-abc0.5Basic
Flask2.0.2Basic
Flask-Babel2.0.0Basic
Flask-Login0.5.0Basic
Flask-Principal0.4.0Basic
greenlet1.1.2Basic
iso-6390.4.5Basic
Jinja23.0.2Basic
lxml4.6.3Basic
PyPDF31.0.3Basic
pytz2021.3Basic
requests2.24.0Basic
six1.16.0Basic
SQLAlchemy1.4.25Basic
tornado6.1Basic
Unidecode1.2.0Basic
Wand0.6.7Basic
Werkzeug2.0.2Basic
-
-
-
-
- - - - - - - - - \ No newline at end of file